当前位置:网站首页>头文件是必须的吗?跟一跟编译过程~~~
头文件是必须的吗?跟一跟编译过程~~~
2022-07-24 17:42:00 【用户6557940】
C/C++中头文件是必须的吗?
不是。
都知道,编译一段代码包括如下阶段:
- 预处理(Preprocessing)
- 编译(Compilation)
- 汇编(Assembly)
- 链接(Linking)
其中,预处理的职责包括展开#define宏定义,处理诸如#if/#ifdef/#ifndef之类的条件编译指令,以及处理#include,将被包含的文件直接插入到预编译指令的位置。当然,预处理过程还负责删除注释等职责。
so?预处理阶段会将#include包含的文件直接插入到源文件.cpp中去。头文件实际上并不会被编译,编译器只会编译源文件。只是在编译之前,会将源文件中#include包含的文件在源文件中展开。(这就好比什么呢?打个不恰当的比方,你在写一篇论文,论文中需要参考Jungle的一篇文章《识别C++代码质量的诀窍,在这里……》。结果预处理的时候,你直接把这篇文章全放到你的论文里了)。
所以,可以手动把头文件中的内容搬到源文件,然后删掉头文件,如下图:
理论上是这样的,而且理论上行得通。但操作起来可不现实,比如,你确定要把下面两个文件搬到源文件中吗?而且头文件中还包含其他头文件,不知道得向上追溯多少级才到头?实际上也没人这么做,Jungle只是想看看这里面的东西。而且这也是头文件存在的必要之处,即,但凡我想在当前源文件中使用其他源文件中的函数、变量,甚至是其他库、系统的函数,我只需要#include相关头文件即可。如果我想在另一个源文件中继续使用,那就再添加#include相关的代码。需要注意的是要避免同一个头文件被重复包含。
#include <iostream>
#include <stdio>接下来再做一个测试:
如上图结构的文件,这次我手动把#include在源文件中展开:
如上图,这也是ok的,可以编译成功。这相当于:
- 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.cpp:
可以看到,编译成功了,生成了main.s汇编文件。
汇编也成功了,生成了目标文件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报错说,未定义的引用func()。上面的ld是链接器,是一个可执行程序,它的输入是一个或多个目标文件,如上面指令中的main.o。
也就是说,目标文件main.o中引用了func(),但链接器找不到它的定义。main.cpp中确实没有func()函数的定义,但func.cpp中有。那不妨我们把func.cpp也编译并生成目标文件func.o,然后链接的时候同main.o一同作为ld的输入:
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>这下成功了,生成了可执行程序app.exe。显然,main.o中引用但未定义的func()被链接器在func.o中找到了。即,链接器在面对一个目标文件时,如果碰到里面有未定义的引用,会在其他目标文件中查找,如果找不到,则报错“undefined reference to”。如果找到有且仅有一个,则pass。
如果找到多个:
如上图,同时在main.cpp和func.cpp中给出了func()函数定义,编译和汇编单个文件都是成功的,但是链接报错说func()有多个定义。而且,链接时输入目标文件的顺序与first defined here相关。
我们还是在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。本文只是就着这个问题,跟了下编译的过程,看看平常开发过程中遇到的编译报错“未定义的引用”、“未声明的变量”这些错误来源是哪原因是什么。
边栏推荐
- Eth POS 2.0 stacking test network pledge process
- JS image conversion Base64 Base64 conversion to file object
- Development Series III of GaN (lapgan, srgan)
- Link editing tips of solo blog posts illegal links
- TCP协议调试工具TcpEngine V1.3.0使用教程
- Extension of ES6 function
- 面会菜评论分析
- 数论整除分块讲解 例题:2021陕西省赛C
- Supervisor common commands
- Colleges and universities have introduced 23 Philippine doctors with heavy funds, and the relevant departments have launched an investigation!
猜你喜欢
随机推荐
AutoCAD - join merge command
NPM install reported -4058 error
Link editing tips of solo blog posts illegal links
2.3.1 view drawing process
Mobile robot (IV) four axis aircraft
DHCP relay of HCNP Routing & Switching
Introduction and use of Pinia
hcip第四天笔记
快速完成intelij idea的单元测试JUnit4设置
HCNP Routing&Switching之DHCP中继
Navicate connects Alibaba cloud (explanation of two methods and principles)
Huawei machine test - topic core test point
Memory allocation and recycling strategy
C语言自定义类型讲解 — 结构体
阿里巴巴1688按关键字搜索商品 API 使用展示
详解 Apache Hudi Schema Evolution(模式演进)
Practical application cases of digital Twins - Smart Park
Getaverse, a distant bridge to Web3
C语言自定义类型 — 枚举
使用Prometheus+Grafana监控MySQL性能指标









