一、概述
一个程序在从源码变成二进制程序之间一共有四个步骤:预处理,编译,生成目标文件,链接。
以下展示了一个简单的加法程序的编译过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 文件 add.h int add(int i, int j); // 文件 add.cpp int add(int i, int j) { return i + j; } // 文件 main.cpp #include <iostream> #include "add.h" using namespace std; int main(){ // 这里是一行注释 cout << add(1, 2) << endl; return 0; } |
二、预处理
预处理阶段主要有以下的操作:
- 头文件替换
- 宏定义替换
- 删除注释
- 预处理指令处理
在GCC中,-E
选项用于预处理,执行g++ main.cpp -E -o main.i
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[ma@dyxmq 0-test]$ wc -l main.i 18399 main.i [ma@dyxmq 0-test]$ tail main.i # 1 "add.h" 1 int add(int i, int j); # 3 "main.cpp" 2 using namespace std; int main(){ cout << add(1, 2) << endl; return 0; } |
可以看到,main.i的内容已经变得十分复杂,预处理后总共有接近两万行。
文件的后就是我们的实际代码区域,其中的头文件和注释也都被处理掉了。
三、编译
预处理完成后需要编译,编译是把程序编程汇编指令的过程。在这一阶段会分析代码,解析语法和错误。
-S
选项用用于生成汇编指令,生成后它依旧还是一个文本文件,执行g++ main.i -S main.s
后的main.s中的内容:
四、生成目标文件
目标文件也就是我们所说的.o
文件了,在编译过后,文件还是不能直接被运行的。生成目标文件的过程会导出属于自己的符号表。这个步骤之后它就已经是一个二进制文件了,里面的符号可以通过诸如readelf
的命令来读取出来。
生成目标文件的选项是-c
,执行g++ main.s -c main.o
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
[ma@dyxmq 0-test]$ g++ main.s -c -o main.o [ma@dyxmq 0-test]$ readelf -s main.o Symbol table '.symtab' contains 23 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS main.cpp 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 3 4: 0000000000000000 0 SECTION LOCAL DEFAULT 4 5: 0000000000000000 1 OBJECT LOCAL DEFAULT 4 _ZStL8__ioinit 6: 0000000000000033 64 FUNC LOCAL DEFAULT 1 _Z41__static_initializati 7: 0000000000000073 21 FUNC LOCAL DEFAULT 1 _GLOBAL__I_main 8: 0000000000000000 0 SECTION LOCAL DEFAULT 5 9: 0000000000000000 0 SECTION LOCAL DEFAULT 8 10: 0000000000000000 0 SECTION LOCAL DEFAULT 9 11: 0000000000000000 0 SECTION LOCAL DEFAULT 7 12: 0000000000000000 51 FUNC GLOBAL DEFAULT 1 main 13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __gxx_personality_v0 14: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z3addii 15: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4cout 16: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEi 17: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZSt4endlIcSt11char_trait 18: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSolsEPFRSoS_E 19: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitC1Ev 20: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _ZNSt8ios_base4InitD1Ev 21: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __dso_handle 22: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __cxa_atexit |
实际上,如果不经过前面的两个步骤,直接对源文件使用-c
也能直接生成目标文件,编译器会自动执行中间的步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[ma@dyxmq 0-test]$ g++ -c add.cpp -o add.o [ma@dyxmq 0-test]$ readelf -s add.o Symbol table '.symtab' contains 10 entries: Num: Value Size Type Bind Vis Ndx Name 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 0000000000000000 0 FILE LOCAL DEFAULT ABS add.cpp 2: 0000000000000000 0 SECTION LOCAL DEFAULT 1 3: 0000000000000000 0 SECTION LOCAL DEFAULT 2 4: 0000000000000000 0 SECTION LOCAL DEFAULT 3 5: 0000000000000000 0 SECTION LOCAL DEFAULT 5 6: 0000000000000000 0 SECTION LOCAL DEFAULT 6 7: 0000000000000000 0 SECTION LOCAL DEFAULT 4 8: 0000000000000000 21 FUNC GLOBAL DEFAULT 1 _Z3addii 9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND __gxx_personality_v0 |
五、链接
链接过程将把多个文件之间的符号表结合起来,生成最终的可执行文件。
1 2 3 |
[ma@dyxmq 0-test]$ g++ main.o add.o # 默认生成a.out可执行文件 [ma@dyxmq 0-test]$ ./a.out # 执行a.out 3 |
评论