深入解析C/C++编译过程:预处理、编译、汇编与链接
深入解析C/C++编译过程:预处理、编译、汇编与链接
C/C++编译过程是将源代码转换为可执行程序的一系列复杂步骤。本文将深入解析这一过程中的四个主要阶段:预处理、编译、汇编和链接。通过具体的代码示例和Linux系统中的g++编译器指令,帮助读者理解每个步骤的具体操作和作用。
一、前言
虽然使用VS这样的集成开发环境(IDE)开发C/C++程序非常简单,只需要点击一下就能完成代码的编译和生成可执行文件,但其底层实现却并不简单。了解编译过程的底层原理,有助于更深入地理解代码,更容易定位和修复bug。
本文将详细介绍C/C++的整个编译过程,主要分为四个步骤:预处理、编译、汇编和链接。以下是这四个步骤的流程图:
本文将以一个简单的C++项目为例进行说明,该项目包含三个文件:
fun.h
:定义函数的头文件fun.cpp
:实现函数的源文件main.cpp
:入口源文件
为了方便查看每一步的处理结果,这些文件在Linux系统中也准备了一份,用于展示每一步编译后的结果。
二、预处理
预处理是编译过程的第一步,主要作用是去除对后续编译无用的内容:
- 去除注释
- 替换宏定义
- 展开头文件
- 执行预处理指令
经过预处理后,代码会变成如下形式:
主要变化包括:
- 注释内容被去除
- 头文件在
.cpp
文件中原地展开 - 宏被替换
- 预处理指令(以
#
开头的命令,如#include
、#define
等)被执行
预处理生成的文件通常以.i
作为后缀名。在Linux系统中,可以使用g++编译器执行预处理:
g++ -E main.cpp -o main.i
g++ -E fun.cpp -o fun.i
-o
选项用于指定生成的结果文件。查看预处理后的文件内容,可以看到预处理指令、注释等已被处理,只剩下代码。
三、编译
预处理完成后,进入编译阶段,目的是将C++代码转换为汇编代码。这一过程会将所有.cpp
源文件编译成存放汇编代码的文件,通常以.s
作为后缀名。
例如,上述两个.cpp
文件会被编译成:
main.s
fun.s
这两个文件中存放的就是其内部C++代码所对应的汇编代码。在Linux系统中,可以使用g++编译器执行编译:
g++ -S main.i -o main.s
g++ -S fun.i -o fun.s
查看汇编代码文件的内容,可以看到已经转换为汇编指令。需要注意的是,不同系统的汇编代码可能有所不同,C/C++的跨平台特性主要体现在源码层面,即同一份C/C++源码可以在不同系统上编译成对应的汇编代码。
四、汇编
计算机只能识别由0和1组成的机器码,汇编阶段就是将汇编代码转换为机器码的过程。生成的文件通常以.o
作为后缀名,表示目标文件。
例如,上述两个汇编文件会被转换为:
main.o
fun.o
这两个文件中存放的就是对应的机器码。在Linux系统中,可以使用g++编译器执行汇编:
g++ -c main.s -o main.o
g++ -c fun.s -o fun.o
由于已经是二进制格式,直接用文本编辑器查看会显示乱码,通常需要使用十六进制查看器来查看二进制文件。
五、链接
最后一步是链接,将所有.o
文件以及用到的库文件代码进行链接,生成最终的可执行程序。例如,代码中使用的iostream
就是标准库,最终会链接标准库的代码。
这个过程类似于“打包”,将所有.o
文件、系统库、标准库等代码整合在一起,生成可执行文件。在Windows系统下,可执行文件通常以.exe
为后缀;在Linux系统下则不使用后缀名。
在Linux系统中,可以使用g++编译器执行链接:
g++ fun.o main.o -o executable
生成的可执行文件可以在相应系统中运行,得到执行结果。
六、总结
从上述过程可以看出,C/C++编译过程非常复杂,但在Windows系统下有Visual Studio等IDE可以简化开发流程。在Linux系统下,通常需要借助make、cmake等工具来组织代码编译,特别是对于包含数十上百个源文件的中大型项目,手动编译会非常繁琐。
本文原文来自kucoding.com