在ANSIC(标准C,ANSI:美国国家标准总局)的任何一种实现中,存在两个不同的环境。
第一种:翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第二次:执行环境,它用于实现执行代码。


我现在使用的是VS2019(IDE)—— 集成开发环境
有很多的功能:
编辑+|编译+链接+调试
编辑器+编译器(cl.ese)+ 链接器(link.exe)+ 调试器
VS已经封装的十分好了,我们无法看到编译的过程,想要刨析这些过程这里在Linux环境下进行演示,下面的演示会在Linux下进行。

gcc -E test.c -o test.i //预编译完成后就停下来,预处理之后产生的结果都放在test.i文件中
gcc -S test.c //编译完成后就停下来,结果保存在test.s中
gcc -c test.c //汇编完成后就停下来,结果保存在test.o中。

代码:
test.c
#include
extern int Add(int, int);
#define MAX 10
int main()
{
//zhushi
int max = MAX;
int a = 10;
int b = 20;
int c = Add(a, b);
printf("%d\n", c);
return 0;
}
Add.c
int Add(int a1, int a2)
{
return a1 + a2;
}
输出结果

可执行程序

为头文件(如:#include
,#include被称为预处理指令)中增加头文件信息,测试后大概增加800多行代码。
经过预编译后,源文件中的注释会被删除。
在代码中所有#define定义的宏就会被替换到文本中原来的位置,而#define行会消失。(#define也被称为预处理指令)
Linux命令




将C语言代码转换为汇编代码需要经过4个步骤
- 语法分析
- 词法分析
- 语义分析
- 符号汇总
- 将源文件转化为可执行程序(可执行程序有自己的格式 —— elf文件的格式,可执行程序内容是二进制的)
- 符号汇总是将代码中全局变量、函数名汇总起来
- 在test.c文件中汇总的是main、Add、printf符号
- 在Add.c文件中汇总的是Add符号
- 这些汇总在编译阶段看不出什么,符号汇总主要体现在汇编和链接阶段
Linux命令
进入test.s文件后,我们会发现代码已经被转化为汇编语言

汇编后的文件是我们之前在翻译环境中提到的目标文件,目标文件的格式也是elf的
- 在Windows环境下的目标文件名是xxx.obj
- 在Linux环境下的目标文件是xxx.o
该文件把汇编指令转换成二进制指令
因为计算机只认识二进制指令
形成符号表
对不同源文件已完成汇总的符号分别进行分配地址,形成符号表
Linux命令

这里我们可以看到,经过汇编后,文件转化为二进制形式
查看readelf工具Linux展示:



查看Add.o符号表:

我们已经知道,每个.o文件都有它对应的符号表,符号表里有编译过程中记录的符号,还有与符号相关联的地址。
在编译过程中,test.i文件里面通过extern来声明Add函数,让编译器知道有这个符号。
但Add函数是定义在其他文件里面的,我们只知道有这么个函数,而不能确定它在哪,所以它对应的地址是无效的(随机分配的地址)

而main函数就在test.c文件中,我们可以找到它,所以我们会给它一个有效的地址。
printf函数是库函数,我们暂时不考虑。

在Add.c文件里实现了Add函数,我们可以找到它的地址,所以在编译过程,Add.i文件会对Add进行符号汇总,Add.o文件会对Add形成符号表。

生成一份可执行程序(格式也是elf的),将不同.o文件中的相同段的数据合并
合并:对所有符号进行合并,将相同的符号进行合并
重定向:使用有意义的地址(在两个源文件中可能出现两个相同的符号,但一个的地址是无意义的)




程序执行的过程:
以上的内容只是程序环境和预处理的冰山一角,了解这些可以让你更好的去理解一些知识,比如说在武侠小说中,少林《易筋经》是无上的内功心法,学会的人无不纵横武林,学其它东西都快的很。如果想要了解的更多建议大家下来的看看《程序员的自我修养》这本书,是一本有关于链接、装载与库的书,虽然前期读会比较吃力,建议大家伴随着整个学习过程去看,相信会对你的内功有很大的提升。