C++编译过程分为四个步骤:分别是预处理(Prepressing) 、编译(Compilation) 、汇编(Assembly) 和链接(Linking),如下图所示:
假如一个文件名为hello.cpp
预编译后的文件
1、预编译
将源代码文件hello.cpp和源文件中使用到的头文件,被预编译期预编译成一个.i文件。对于C++程序来说,可能源代码文件的扩展名可能是.cpp或者是.cxx,头文件的扩展名可能是.hpp,而预编译后的文件扩展名是.i。第一步预编译的过程相当于如下命令:
cpp hello.cpp > hello.i
或者
gcc -E hello.cpp -o hello.i
预编译过程主要是处理那些源代码文件中以#开始的预编译指令,比如#include、#define等,主要是处理以下规则:
- 将所有的#define删除,并且展开所有的宏定义。
- 处理所有条件预编译指令,比如#if、#ifdef、#elif、#else、#endif。
- 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置,这个过程是递归进行的,就是说被包含的文件可能还包含其他文件。
- 删除所有的注释。
- 添加行号和文件名标识,比如#2“hello.c”2,以便于编译时编译器产生调试用的行号信息以及用于编译时产生编译错误或者警告时能够显示行号。
- 保留所有的#pragma编译指令,因为编译器需要使用他们。
经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.i文件中。所以当我们无法判断宏定义是否正确或者头文件包含是否正确时,可以查看预编译后的文件来确定问题。
2、编译
编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件。
汇编输出文件后缀为.s。
gcc -s hello.i -o hello.s
3、汇编
汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,没有复杂的语法,也没有语义,不需要做指令优化,只需要根据汇编指令和机器指令的对照表一一翻译就好了。“汇编”这个名字也来源于此。上面的汇编过程我们可以调用汇编器as来完成,其输出的文件称为目标文件(Object File),后缀为.o:
as hello.s -o hello.o
或者
gcc -c hello.s -o hello.o
或者使用gcc命令从源代码文件开始,经过预编译,编译,和汇编直接输出目标文件:
gcc -c hello.cpp -o hello.o
4、链接
链接就是将程序运行需要的外部资源和程序二进制代码链接在一起,保证程序正确运行。链接之后输出.out或者.exe可执行文件。
链接分为两种:
- 静态链接:代码从其所在的静态链接库中拷贝到最终的可执行程序中,在该程序被执行时,这些代码会被装入到该进程的虚拟地址空间中。windows下以.lin为后缀,linux以.a为后缀。
- 动态链接:代码被放到动态链接库或者共享对象的某个目标文件中,链接程序只是在最终的可执行程序中记录了共享对象的名字等一些新消息。在程序执行时,动态链接库的全部内容会被映射到运行时相应的虚拟地址的空间。windows以.dll为后缀,linux以.so为后缀。
两种链接方式的优缺点:
- 静态链接:浪费空间,每个可执行程序都会有目标文件的一个副本,这样如果目标文件进行了更新操作,就需要重新进行编译链接生成可执行程序(更新困难),优点是执行的时候运行速度快,因为可执行程序具备了程序运行的所有内容。
- 动态链接:节省内存,更新方便,但是动态链接是在程序运行时,每次执行都需要链接,相比静态链接会有一定的性能损失。
链接方式的时机:
- 静态链接:是在形成可执行程序之前。
- 动态链接:程序执行时。
参考:C/C++ 程序编译过程简述 | GuKaifeng's Blog