当我们将代码写出来以后,文件的后缀通常是.c或者.h,计算机并不能直接运行这两个后缀的文件。那么我们的代码是如何经过编译和链接形成计算机能够运行的程序呢?
我们已经知道,一个项目中,包括许多不同的.c文件和.h文件。这些文件相互之间,通过声明引用联系在一起,而把这些声明引用链接在一起就需要链接器,这个过程就是链接,这个过程也会涉及一些库函数,我们把这些运行时库或第三方库称为链接库。而链接器并不能识别.c和.h文件,我们需要将这些文件转化成链接器能识别的.obj文件,也叫目标文件,这个转化的过程就是编译。而每个.c和.h文件都能生成对应的目标文件,即有多少.c .h文件就会生成多少目标文件。
下面具体介绍:
一、编译
编译分为三个过程:预编译(预处理)、编译、汇编
1.预编译
预编译主要处理以#开始的预编译指令,处理的规则如下:
- 将所有的#define删除,并展开所有的宏定义。
- 处理所有的条件编译指令,如:#if、#ifdef、#elif、#else、#endif。
- 处理#include预编译指令,将包含的头文件的内容插入到该预编译指令的位置。这个过程是递归进行的,也就是说被包含的头文件也可能包含其他文件。
- 删除所有的注释
- 添加行号和文件名标识,方便后续编译器生成调试信息等。
- 或保留所有的#pragma的编译器指令,编译器后续会使用。
经过预编译的文件后缀会变为.i
2.编译
经过预处理以后,程序就会对文件进行词法分析、语法分析、语义分析和优化
词法分析,就是将我们写的代码划分成一个一个的符号,就像英语中,将一句话划分成若干个单词。例如:a = 10; a是标识符 =是赋值 10是数字
语法分析,以表达式为节点,形成语法树。例如:a = 1 + 2; 就会形成如下的语法树:
语义分析,就会具体进行声明和类型的匹配,例如:a为整型,1为整型,2为整型,=和+也为整型。在这个阶段就会报告错误的语法信息。
最后形成.s的文件
3.汇编
汇编就会将经过编译的代码直接进行机器指令的转化,将汇编指令和机器指令进行一对一的转化,形成.obj文件
二、链接
经过编译以后,就来到链接的过程,链接需要解决一个项目中多文件、多模块相互调用的问题,这个过程包括地址和空间分配,符号决议和重定位等这些步骤。
例如:在A文件中引用了一个名为B的函数,而B函数的主体储存在C文件中,因此就需要对B函数进行地址和空间分配,从而使程序正常运行。
那么我们发现函数未定义,就会在链接这个过程中发现。
由于链接过程较为复杂,读者可阅读《程序的自我修养》详细了解。