关于头文件是否参与编译的讨论

来源:互联网 发布:土木工程软件 编辑:程序博客网 时间:2024/06/05 14:27

一、文章来由

写项目的时候发现了这个问题,又是一个比较底层的问题,首先说明,这篇文章只是我根据查阅的资料和做的实验提出的一个讨论,并不一定就是正确答案。因为这个问题网上众说纷纭,我很欢迎大家参与这个讨论,一起搞懂这个问题~~~

二、问题的提出

问题就是。。。

2.1 问题1(主问题):

头文件是否真正参与编译?

先上一个网上的标准答案:

.h的内容被插入到.c中,作为.c的内容被编译。.h文件本身不直接参加编译。

据我理解,这句话就是说明了头文件不直接参与编译,是作为一个插入来理解。

也就是说:

是要编译的,只不过这些头文件是预编译的。每个源文件包涵的头文件都会被预编译包含到源文件中去。

这样就又牵出来三个子问题~~

2.2 问题2:

预编译是什么?

网上的答案大致理解为类似复制粘贴的操作,这样理解是有理由的,因为

(1)头文件不一定是 .h 文件,可以是任意类型

(2)头文件可以定义一些很奇怪的东西,见下面的代码

// testheadcompile.h1, 2, 3, 4, 5//main.cpp#include <iostream>using namespace std;int main(){    int a[] = {        #include "testheadcompile.h"    };    cout<<a[1]<<endl;    return 0;}

分析:
这段代码可以说是真的变态,因为头文件写在了函数体内,这么说的确就像是一个复制粘贴,关键是可以跑出结果,我在vs2012 release模式下,结果如下:

这里写图片描述

而且还被360误认为是木马。。。

于是我这样认为:如果需要什么东西(变量或者方法)的时候,就直接可以像利用 #define 一样用 #include 了?而且#include,有井号本身就是宏的写法~~

但是发现,把 include 像上面一样写在方法体内,定义变量可以,函数不行,否则会报“本地函数定义是非法的”的错,但如果 include 在开头,就可以正常使用函数。这又是一个预处理的典型代表,说明了在函数里面定义了函数~~~

其实甚至可以写出这种鬼畜的代码:

// testheadcompile.h1, 2, 3, 4//main.cpp#include <iostream>using namespace std;int main(){    int a[] = {        #include "testheadcompile.h"        ,5    };    cout<<a[4]<<endl;    return 0;}

答案是5

2.3 问题3:

既然上面说是预编译,或者说是单纯的复制,那么那些没有实现的函数预编译?编译?链接的时候编译器怎么做的?那些实现了,在整个过程没有用到的函数呢???

这个问题,我是这样看的:

所有的编译都是单独的,没有实现的函数,就没有单独实现的编译,如果用到了,找不到实现体,就会报链接错误,也就是vs里面常见的“fatal error LNKXXX: N 个无法解析的外部命令”。。。

而对于头文件的作用仅仅是为了在编译的时候告诉编译器,这里如果用到一些其他文件的东西,我实现了,编译别报错!

所以这么说,实现了,但在整个过程没有用到的函数,是编译了的,但没有被链接进最后的可执行文件。(可能有不对的地方,欢迎指正)

2.4 问题4:

既然说没有参与编译,只是参与预编译,但是可以在头文件里面定义函数,又如何解释?

首先我来回答一下这个问题,个人感觉是参与编译的,但是是头文件被包含到源文件进行编译,也就是说编译器只会编译源文件,不被包含的头文件是没有存在的意义的,因为我故意把头文件写错,然后这样报错。。。

这里写图片描述

刚刚测试发现了一个现象,没有cpp文件,vs也可以编译,我怀疑在编译的时候,默认将main函数所在的文件作为编译文件,即使main函数写在头文件,也可以正常执行。

而在Linux下面,用编译器编译时需要指定编译的文件,所以有没有后缀名是无关紧要的,反正都是文本文件,读取方式就是确定的,所以Linux对于后缀名并不重要。

这里写图片描述

1 0