版权声明:本文为优快云博主「M_jianjianjiao」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接https://blog.youkuaiyun.com/M_jianjianjiao/article/details/84109955
通常我们使用头文件时都是在头文件中进行声明,在源文件中定义,哪我们能否在头文件中进行函数的定义
- 我们先进行一个测试,先声明一个test.h和一个test.cpp文件,并且在test.h中定义一个函数和一个变量
可以发现,程序运行没有问题,结果也正确
- 再创建一个test.cpp文件,并且同时包含tset.h,再次运行
此时程序运行出错,提示出现了重定义的错误
可能有的同学会疑惑,不是已经使用了预处理指令来防止头文件重复包含了吗
#ifndef __TEST_H__
#define __TEST_H__
//头文件中的内容
#endif
- 1
- 2
- 3
- 4
使用此预处理指令的作用
防止头文件被重复包含,假如一个源文件中同时包含了头文件a和头文件b ,而头文件b中又包含a,哪在头文件展开时就会包含两份的头文件a,这样不仅浪费时间还降低了效率,而引入预处理指令就很好的解决了这个问题。
预处理的时间与范围
编译器在执行一个程序时大该分为以下几个步骤
预处理阶段:
预处理阶段是编译前的准备工作主要进行一下几个工作:
- 执行预处理指令
- 将头文件展开 即#include 所包含的内容
- 进行宏替换
- 删除注释
- 添加行号和标识
注意:预处理阶段是各个源文件独自进行的,每个源文件互不干扰,预处理的作用范围仅在本文件中,在预处理完成后,会生成一个.i文件,而这个.i文件就是下一阶段编译时的一个编译单元,在此之后头文件就已经没有任何的作用了
编译
编译时是以编译单元为单位基本单位进行的,而编译单元即为预处理结束后每个.cpp 文件生成的.i 文件,编译时有以下工作
- 函数和变量声明的检查
- 语法分析 语义分析 词法分析 符号汇总等
- 将代码转换为汇编语言
注意:编译时仅对函数和变量的声明进行检查,而不关心函数的定义,编译是以一个个单独的文件为单元的,与预处理一样,而这些编译单元都是之前生成的.i 文件。编译完成后会生成.s文件
汇编
将编译后生成的.s文件进行汇编
- 转成机器指令
- 生成符号表
- 符号地址与地址重定位
在编译时会将地址用符号替代,编译器会将其翻译成虚拟地址
而地址的重定位就是建立物理内存与虚拟地址间的映射
在经过汇编阶段后,每个.s会生成一个对应的二进制的.o 文件
链接
- 在编译完成后,生成了目标文件,此时就需要将这些目标文件链接起来生成可执行文的.exe件。
通过以上的编译器处理程序的过程,我们可以知道在链接之前,各个文件都是相互独立的,互不干扰。
此时,我们也就知道
- 头文件是在预处理时起作用,而它的作用范围仅在当前文件
- 编译时对于函数的定义并不关心,只关心声明,作用范围也仅在当前文件。
- 而链接时才会将个个文件相互关联
那么之前的问题也就有答案了
因为在头文件中定义了一个函数,在预处理时头文件展开,每个文件都有了一个该函数的定义,因为编译时是分隔的,所以到链接时,将所有文件关联在一起时,发现每个包含了该头文件的文件中有一个相同函数的声明,编译器就会报出重定义的错误。
当只有一个源文件时,因为没有别的源文件的冲突所以不会报错
总结:
- 声明是在编译时处理
- 定义是在链接时处理
- 链接之前所有文件是隔离的
所以在头文件中尽量不要进行函数的定义,只对其进行声明。否则如果有多个源文件链接时会报错