在ANSI C的任何一种实现中,存在两个不同的环境:
- 翻译环境,在这个环境中源代码被转换为可执行的机器指令。
- 执行环境,它用于实际执行代码。
翻译环境包括编译和链接两个过程,一个.c文件经过翻译环境(即编译器和链接器)后,最终变成一个.exe文件。
编译是将源代码翻译成目标代码(即机器语言)的过程。
链接是将多个目标文件和库文件链接成一个可执行文件的过程。
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中。
VS中的编译器叫cl.exe,链接器叫link.exe。
- 头文件的包含
- 注释的删除
- #define定义的符号的替换
及预处理指令相关(#pramga pack等)
编译即把C语言代码转换成二进制代码
- 语法分析
- 词法分析
- 语义分析
- 符号汇总(全局符号)
生成.o文件,把汇编代码转换成二进制的指令
形成符号表
在vscode写这样一段代码:
#include //头文件
#define M 1
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
for(i = 0; i < 10; i++)//打印
{
printf("%d ",arr[i]+M);
}
return 0;
}
用指令将预处理(-E)的结果输出(-o)到test.i文件里,可以看到,前面一大堆的内容其实是头文件的内容(#include),最后才是test.c中的代码,与test.c不同的是,注释被删除,#define定义的符号被替换。
用gcc test.i -S指令,会生成一个test.s的文件。
这其实是把C语言代码翻译成了汇编代码,在汇编过程中,进行了语法分析,词法分析,语义分析,符号汇总相关工作。
假如有这样一段代码:
#include
extern int Mul(int x,int y);
int Add(int x,int y)
{
return x+y;
}
int g_val = 12;
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
for(i = 0; i < 10; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
符号汇总便是把Mul、Add、g_val、main汇总起来。
用gcc test.i -c指令,会生成一个test.o文件(VS上是.obj),即目标文件(二进制文件)。
对每个.o文件,都会根据汇总的符号形成符号表
把所有.o文件链接起来,生成.exe可执行程序
在这个过程中要进行:
- 合并段表:合并段,调整段偏移
- 符号表的合并和重定位:汇总所有符号,定位正确的虚拟地址
执行环境是程序在运行时所处的环境,包括操作系统、硬件平台、运行库、系统配置等因素。程序在不同的执行环境下可能会有不同的行为、性能和可移植性,因此程序员需要考虑执行环境的影响,并编写可移植的代码。