全文目录
- 前言
- 翻译环境和执行环境
- 编译和链接
- 预编译(预处理)
- 编译
- 汇编
- 链接
- 总结
翻译环境:
在这个环境中源代码被转换为可执行的机器指令(二进制的指令)。
执行环境:
它用于实际执行代码。
我们日常使用的VS2019就是一个集成开发环境,结合了编辑、编译、链接、调试等多种功能,其中编译使用的是 cl.exe
, 链接使用的是 link.exe
文件中,不同的编辑器使用的可能不同。
object
code
)。linker
)捆绑在一起,形成一个单一而完整的可执行程序。其中编译又分为:预编译、编译、汇编 三步操作。
为了方便演示,接下来使用Linux下的gcc进行实验。
实验代码:
// test.c
#include
extern Add(int a, int b);
// 测试注释和#define
#define Max 100
int main()
{
int z = Max;
int a = 10;
int b = 20;
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
// add.c
int Add(int a, int b)
{
return a + b;
}
我们可以使用下面的指令将程序编译停留在预编译后:
gcc test.c -E -o test.i
gcc add.c -E -o add.i
打开test.i
可以发现多了很多行,同时注释的代码和 #define
都不见了:
再从/usr/include
这个路径下打开stdio.h
这个文件可以发现 test.i
中多出来的就是stdio.h
的内容
那么就可以确定预编译阶段进行了一下几个操作:
#include
)#define
定义符号的替换以上三个都是属于文本操作
将程序停留在编译之后:
gcc test.i -S -o test.s
gcc add.i -S -o add.s
打开test.s
可以看到:
这些都是之前在VS 中看到的反汇编。也就是说编译将C语言代码翻译成了汇编代码。其过程相当复杂,主要是做了一下几个操作:
符号汇总:将文件中的全局符号汇总出来(局部的符号不管), 基本上就是函数名
将程序停留在编译之后:
gcc test.s -c -o test.o
gcc add.s -c -o add.o
生成的就是目标文件。在Windows下目标文件的后缀时.obj
,Linux下的后缀时.o
。
打开test.o
:
发现全是乱码,也就是说汇编将汇编指令翻译成了二进制的指令。
但是这时候在编译阶段进行的符号汇总就派上用场了,这些符号在汇编阶段被制成了符号表。二进制文件我们时看不懂的,在Linux下可执行程序的格式是:elf
,所以我们可以借助readelf
来查阅可执行文件:
readelf -s test.o
readelf -s add.o
汇编就是对每个编译阶段汇总的符号赋予地址(如果在文件中找不到该符号的有效内容,赋予无效地址),即:
链接阶段做的就是:
合并段表就是将每个目标文件的各个段整合起来,符号表的合并就是将各个目标文件的符号表合并成一个表,并检查每个符号的地址:
程序的编译和链接过程是很复杂的,能力有限,只能学习这些大概的概念。
Linux 指令汇总:
// 编译的各个阶段:
ESc ——> iso
// 查看目标文件:
readelf -[options] filename
// 头文件路径:
/usr/include