C语言编译过程

编译过程

总览图

C语言编译过程_第1张图片

预处理

在此阶段过程中,编译器会根据我们写好的代码,以此分析其中的语句,并对当中的某些语句执行替换,该替换是直接作用于.c文件。

分别处理:注释、#define、条件编译指令、#include。

#include
#define MAXLENGTH 20
int main()
{
	int i = 0;
	int j = MAXLENGTH;
#ifndef GOOGLE_VERSION
	i +=j;
#endif
	printf("%d", i);//未定义GOOGLE_VERSION 输出20
	return 0;
}

在VS下点击“生成解决方案”会执行编译操作,编译第一步执行的便是预处理,生成预处理文件(VS下需进行些许设置才可看见)

相应设置:资源管理器下右击工程->选择属性->点击C/C++->选中预处理器->更选“预处理到文件”属性为是->点击应用再确定。

C语言编译过程_第2张图片

文件路径:再次点击生成解决方案->右键工程->选中“在文件资源管理器中打开文件夹(X)”->Debug目录下找到.i后缀的文件->.i后缀的文件便是通过了预处理之后的文件(该文件的代码量为膨胀为.c文件的好几倍)。

C语言编译过程_第3张图片

编译

编译过程按顺序分两步:语句分析和代码优化。

语句分析:

对整体代码进行扫描处理,解析代码的词法、语法、语义,以此排除不合法的、不规范的代码。

词法解析:关键字的正确性、标识符的有效性、立即数的展开。

语法分析:变量命名规范、程序结构合法性、函数定义的正确性、重定义现象等。

语义分析:表达式的合理性、变量的未初始化使用等。

如若以上情况存在不合法性,编译器会停止编译并抛出错误同时显示该错误。

代码优化:

语句分析通过了之后,编译器会针对现有代码最后生成的汇编语句来进行优化处理。存在两种模式的代码优化方式:Debug和Release。

Debug:调试版本,生成便于调试的汇编指令;会根据现有的每一句代码生成对应的汇编语句,存在将现有的一句语句代码细化为几句汇编语句。旨在方便开发者逐句调试代码。

如下为以上程序的Debug版本下的反汇编代码:

#include
#define MAXLENGTH 20
int main()
{
01061790  push        ebp  
01061791  mov         ebp,esp  
01061793  sub         esp,0D8h  
01061799  push        ebx  
0106179A  push        esi  
0106179B  push        edi  
0106179C  lea         edi,[ebp-0D8h]  
010617A2  mov         ecx,36h  
010617A7  mov         eax,0CCCCCCCCh  
010617AC  rep stos    dword ptr es:[edi]  
	int i = 0;
010617AE  mov         dword ptr [i],0  
	int j = MAXLENGTH;
010617B5  mov         dword ptr [j],14h  
#ifndef GOOGLE_VERSION
	i += j;
010617BC  mov         eax,dword ptr [i]  
010617BF  add         eax,dword ptr [j]  
010617C2  mov         dword ptr [i],eax  
#endif
	printf("%d", i);//未定义GOOGLE_VERSION 输出20
010617C5  mov         esi,esp  
010617C7  mov         eax,dword ptr [i]  
010617CA  push        eax  
010617CB  push        10658A8h  
010617D0  call        dword ptr ds:[1069114h]  
010617D6  add         esp,8  
010617D9  cmp         esi,esp  
010617DB  call        __RTC_CheckEsp (010611D1h)  
	return 0;
010617E0  xor         eax,eax  
}
010617E2  pop         edi  
010617E3  pop         esi  
010617E4  pop         ebx  
}
010617E5  add         esp,0D8h  
010617EB  cmp         ebp,esp  
010617ED  call        __RTC_CheckEsp (010611D1h)  
010617F2  mov         esp,ebp  
010617F4  pop         ebp  
010617F5  ret 

Relase:发行版本,不会为无效的过程操作(与结果无关)生成汇编指令,旨在降低程序的内存、提高程序的运行速度。

如下为以上程序的Relase版本下的反汇编代码:

	int i = 0;
	int j = MAXLENGTH;
#ifndef GOOGLE_VERSION
	i += j;
#endif
	printf("%d", i);//未定义GOOGLE_VERSION 输出20
00FB1000  push        14h  
00FB1002  push        0FB2100h  
00FB1007  call        dword ptr ds:[0FB2090h]  
00FB100D  add         esp,8  
	return 0;
00FB1010  xor         eax,eax  
}
00FB1012  ret  
--- f:\dd\vctoo

汇编器

针对生成的汇编代码,将其逐句解析转换生成一一对应的机器码(二进制编码)。

链接

由于在现有代码中使用了大量的外部指令(库或目标文件),而这些外部指令并非存在于我们本地代码中,因此没有生成的与之对应的机器码,如若没有与之对应的机器码,则程序无法正常运行,因此在将本地代码转换成汇编指令之后进入链接的过程操作,该操作由链接器来完成。

存在两种链接方式:静态链接和动态链接。

静态链接:

静态连接是在链接时将使用了的外部指令(库或目标文件的内容)加入到可执行程序(转换生成的二进制编码)中,即通过链接器将该文件与汇编器转换生成的汇编指令链接到一块生成可执行程序。

动态链接:

动态链接,在可执行文件装载时或运行时,由操作系统的装载程序加载外部指令的相应内容(比如:库文件,是预先编译链接好的可执行文件),存在在同一时间,多个应用可以使用一个外部指令的同一份拷贝,而操作系统不需要加载该指令的多个实例,即不再将他们的多个实例静态的对应链接在一起,而是在程序要运行时才进行链接对应的同一份拷贝指令。

二者之间区别:

  1. 静态链接会使得生成的可执行文件变大,占用更多的系统资源,该执行文件包含相同的公共代码,会造成浪费。
  2. 动态链接是由操作系统负责立即解析外部指令,然后代表应用调用合适的文件内容。由于每个调用都会存在系统开销,因此运行时链接的执行效率要慢得多,对应用的性能有负面影响。

你可能感兴趣的:(C语言)