C/C++中头文件是必须的吗?
不是。
都知道,编译一段代码包括如下阶段:
预处理(Preprocessing)
编译(Compilation)
汇编(Assembly)
链接(Linking)
其中,预处理的职责包括展开#define宏定义,处理诸如#if/#ifdef/#ifndef之类的条件编译指令,以及处理#include,将被包含的文件直接插入到预编译指令的位置。当然,预处理过程还负责删除注释等职责。
so?预处理阶段会将#include包含的文件直接插入到源文件.cpp中去。头文件实际上并不会被编译,编译器只会编译源文件。只是在编译之前,会将源文件中#include包含的文件在源文件中展开。(这就好比什么呢?打个不恰当的比方,你在写一篇论文,论文中需要参考Jungle的一篇文章《识别C++代码质量的诀窍,在这里……》。结果预处理的时候,你直接把这篇文章全放到你的论文里了
#include <iostream>
#include <stdio>
如上图结构的文件,这次我手动把#include在源文件中展开:
main.cpp中首先添加了func()函数声明,然后在main()函数中调用了func()。
func.cpp中也添加了func()函数声明,同时给出了func()函数的定义。其实这里的声明可以不要了,直接给func()函数的定义。当然,你也可以声明多次。
那么main.cpp中能否也把func()声明删掉呢?
看来不行,报错说在该作用域内func没有声明。注意我这里是单独编译main.cpp,加上func.cpp也是一样的:
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -o app main.cpp func.cpp
main.cpp: In function 'int main(int, char**)':
main.cpp:5:5: error: 'func' was not declared in this scope
func();
首先预处理肯定没问题,预处理只是原地展开而已。而且上面的测试我在main.cpp中删掉了func()声明,就等于在main.cpp中删掉#include。所以可以认为“没有预处理过程”(实际上是有的,因为预处理过程还负责生成行号等等职责)。
那是编译过程出的错吗?不妨单独看看是否能够编译成功:
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -S main.cpp
main.cpp: In function 'int main(int, char**)':
main.cpp:5:5: error: 'func' was not declared in this scope
func();
^~~~
oh,是编译阶段出的错,报错信息跟上面是一样的(废话)。编译过程包括词法分析、语法分析、语义分析、代码优化及目标代码生成等过程。这里的目标代码是汇编代码,所以g++ -S会产生一个汇编文件。
在这里,func是一个未经声明就使用的东西(实际上,如果在main()函数中直接写一行a=10会报相同的错,即'a' was not declared in this scope),在语义分析阶段会被检查出来。
声明变量可以告诉编译器这个变量类型是什么,占多少个字节。声明函数则可以告诉编译器函数名是什么、返回类型是什么、参数个数、参数类型是什么。不声明就使用,别人怎么知道func是什么东西呢?
汇编也成功了,生成了目标文件main.o。
可链接报错了:
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -S main.cpp
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -c main.s
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -o app main.o
main.o:main.cpp:(.text+0x15): undefined reference to `func()'
collect2.exe: error: ld returned 1 exit status
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -S main.cpp
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -c main.s
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -S func.cpp
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -c func.s
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -o app main.o func.o
PS F:\Jungle\1.Program\4.C++\4.Compiler>
我们还是在main.cpp中只保留func()函数的声明,再单独编译汇编生成main.o。接下来用nm看下main.o符号表中的内容:
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -S .\main.cpp
PS F:\Jungle\1.Program\4.C++\4.Compiler> g++ -c main.s
PS F:\Jungle\1.Program\4.C++\4.Compiler> nm main.o
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 p .pdata
0000000000000000 r .rdata$zzz
0000000000000000 t .text
0000000000000000 r .xdata
U __main
U _Z4funcv
0000000000000000 T main
PS F:\Jungle\1.Program\4.C++\4.Compiler>
其中:U代表该符号在当前文件中是未定义的。如果在main.cpp中加上func()函数定义,再尝试上面步骤,得到:
0000000000000000 b .bss
0000000000000000 d .data
0000000000000000 p .pdata
0000000000000000 r .rdata$zzz
0000000000000000 t .text
0000000000000000 r .xdata
U __main
0000000000000000 T _Z4funcv
0000000000000002 T main
可以看到,符号_Z4funcv前面的标识变为T了,标识该符号位于代码段text section。
再跟下去就讲不完了。。。
回到题目上来,头文件是必须的吗?不是,头文件会在预处理阶段被展开。但头文件会我们编程带来极大便利,要使用某个函数、某个变量了,那就#include。本文只是就着这个问题,跟了下编译的过程,看看平常开发过程中遇到的编译报错“未定义的引用”、“未声明的变量”这些错误来源是哪原因是什么。
联系客服