编译全过程
C++编译过程:预处理、编译、汇编、链接四个步骤
1.预处理
预处理阶段主要处理预处理指令,比如文件包括(#include)、宏定义(#define)、条件编译。
1.1优点
i. 方便程序的修改,如用宏定义定义常量,既可达到修改一处,多处改变的效果。
ii. 提高程序的运行效率,比如通过使用带参数的宏定义可以完成函数调用的功能,而又避免可函数调用带来的开销(如保留调用函数的现场和恢复调用函数的现场所带来的开销)。
1.2缺点
i. 宏定义是通过在预处理时直接替换,并不会检查参数是否合法,存在安全隐患。
ii. 嵌套宏定义过多可能会影响程序的可读性,很容易出错,不容易调试。
1.3注意:
a.宏常量和const常量的区别
1.有无数据类型:const 常量有数据类型,而宏常量没有数据类型
2.编译器是否会进行安全检查:编译器可以对const 常量进行类型安全检查。而对宏常量只进行字符替换,没有类型安全检查,
3.是否可以调试:有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
b.#define与typedef的区别
1.原理不同:
define是预处理指令,进行简单的字符串替换,没有数据类型;不做安全性检查,只有在编译时才能发现可能的错误
typedef是关键字,在编译时处理,给已知的数据类型取别名(比如ITK库中特别长的数据类型取简单名字,增强代码可读性);不能在函数体内部使用typedef。
2.功能不同
#define可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。
在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型,而 #define 则无法保证。
c.带参数宏定义和内联函数的区别
内联函数有数据类型,可以检查参数是否合法,而带参数宏定义不会
带参数宏定义是在预处理阶段展开的,而内联函数实在编译阶段展开的,两种都不会带来函数调用的开销。
注:内联函数的定义与普通函数基本相同,只是在函数定义前加上关键字 inline。
内联函数最初的目的:代替部分 #define 宏定义;提高运行效率的同时,避免define的弊端(安全性检查、不能使用return控制流程,无法操作类的私有数据成员等)
使用内联函数替代普通函数的目的:提高程序的运行效率;
inline void print(char *s)
{
printf("%s", s);
}
2.编译
编译阶段进行语法分析、词法分析和语义分析,并且将代码优化后产生相应的汇编代码文件(ASCLL文件),linux——.s 文件。
3.汇编
通过不同平台(Windows、Linux)的汇编器将汇编代码翻译成机器码,即生成二进制可重定向文件(linux——.o文件)。
4.链接
链接是将所有的linux的.o文件 or Windows下的.obj文件和库(动态库dll、静态库lib)链接在一起,得到可以运行的可执行文件(Windows的.exe文件或Linux的.out文件)等
4.1 程序的链接阶段可分为两个步骤:
第一步:由于每个.o文件都有都有自己的代码段、bss段,堆,栈等,所以链接器首先将多个.o 文件相应的段进行合并,建立映射关系并且去合并符号表。进行符号解析,符号解析完成后就是给符号分配虚拟地址。
第二步:将分配好的虚拟地址与符号表中的定义的符号一一对应起来,使其成为正确的地址,使代码段的指令可以根据符号的地址执行相应的操作,最后由链接器生成可执行文件。
4.2 链接的方式
4.2.1 静态链接
4.2.2 动态链接
i. 动态链接库的优点:(1)更加节省内存;(2)DLL文件与EXE文件独立,只要输出接口不变,更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性。
ii. 动态链接库的缺点: 使用动态链接库的exe应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。
iii. 静态链接库的优点: (1) 代码装载速度快,执行速度略比动态链接库快; (2) 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。
iv. 静态链接库的缺点: 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费。
参考文献
C/C++:编译全过程——预处理、编译、汇编、链接(包含预处理指令:宏定义,文件包括、条件编译)_怎么在devc++里面把c文件变成汇编语言-CSDN博客