0 概述
四个步骤:预处理(Preprocessing)-> 编译(Compilation)-> 汇编(Assembly)-> 链接(Linking)
1 预处理
输入:原始的C++源文件(.cpp, .h, .hpp)
输出:经过处理的“纯”C++文件(通常为 .i 或 .ii 后缀)
g++ -E main.cc -o main.i预处理器会对源代码进行文本级别的操作:
展开头文件 (
#include):将#include指令替换为所包含头文件的实际内容。这是一个递归的过程,如果头文件还包含了其他头文件,也会一并展开。宏替换 (
#define):展开所有的宏定义,并将代码中使用的宏替换为其定义的值或代码片段。条件编译 (
#if,#ifdef,#ifndef,#else,#elif,#endif):根据条件编译指令,保留满足条件的代码块,删除不满足条件的代码块。这在跨平台开发和编写Debug/Release版本时非常有用。处理特殊指令:处理
#pragma等特殊指令。删除注释:将所有注释(
//和/* ... */)替换为一个空格或直接删除。
2 编译
输入:预处理后的文件(.i)
输出:汇编代码文件(.s)
g++ -S main.i -o main.s
# 或 (可以从 .cpp 直接开始,g++ 会自动先预处理)
g++ -S main.cpp -o main.s编译器会将“纯”C++代码翻译成特定处理器架构的汇编语言:
词法分析 (Lexical Analysis):将源代码的字符序列分割成一系列的“词法单元”(token),如关键字、标识符、运算符、常量等。可以把它想象成将一句话拆分成一个个独立的单词。
语法分析 (Syntax Analysis):根据C++的语法规则,将词法单元流组织成一个“抽象语法树”(Abstract Syntax Tree, AST)。如果代码有语法错误(比如缺少分号、括号不匹配),就会在这个阶段被发现并报告。
语义分析 (Semantic Analysis):检查语法树在语义上是否合法,比如变量是否已声明、类型是否匹配、函数调用参数是否正确等。确保代码“言之有物”。
中间代码生成与优化:生成一种中间表示(如LLVM IR),并对其进行各种优化,比如删除无用代码、计算常量表达式等,以提高最终代码的执行效率。
3 汇编
输入:汇编代码文件(.s)
输出:目标文件(.o 或 .obj)
g++ -c main.s -o main.o
# 或 (可以从 .cpp 直接开始,g++ 会自动预处理和编译)
g++ -c main.cpp -o main.o将汇编代码翻译成机器可以执行的指令,即机器码(二进制代码)。
但是,这个目标文件还不能直接运行,因为它可能引用了其他文件中的函数或变量(比如 printf),但还不知道它们的实际地址;多个目标文件还没有被组合在一起。
4 链接
输入:一个或多个目标文件(.o)、库文件(.a 静态库, .so 动态库)
输出:最终的可执行文件(a.out, .exe 或无后缀)
g++ main.o helper.o -o myprogram合并目标文件:将多个目标文件合并成一个可执行文件或库文件。
符号解析 (Symbol Resolution):解析所有目标文件中的“符号”(主要是函数和变量的名字)。如果某个符号在一个目标文件中被引用,链接器必须在其他目标文件或库中找到它的定义。如果找不到,就会报“未定义的引用”(undefined reference)错误。
重定位 (Relocation):在编译和汇编阶段,生成的代码和数据的地址都是从0开始的。链接器在合并所有段之后,会为它们分配最终的内存地址,并修正所有代码中对符号地址的引用,使其指向正确的位置。
链接库文件:
静态链接:将静态库(
.a)中的代码直接复制到最终的可执行文件中。优点是独立性强,缺点是文件体积大。动态链接:只在可执行文件中记录共享库(
.so/.dll)的名字和引用信息,在程序运行时才由操作系统加载所需的共享库。优点是节省磁盘和内存空间,便于库的更新。
评论区