c文件是源码文件。里面的书写是相对来说,最接近人类语言的C语言。
我们使用gnu工具链来使之生成二进制文件。然后,计算机机器就可以执行它。
也许你认为很简单,只需要执行gcc hell.c就可以生成二进制文件了,而如果你使用的是IDE集成开发环境的话,那么,也许更简单,只需要编译构建就可以了。然而,事实并非如此简单。以上过程,可以分解为4个步骤,分别是:
1. 预处理prepressing
2. 编译compilation
3. 汇编assembly
4. 链接linking
预编译
首先是源代码hello.c和相关的头文件,如stdio.h等被预编译器cpp预编译成一个.i文件。
预编译过程主要处理那些源代码文件中的以‘#’开始的预编译指令。比如“#include”、“#define”等,具体规则如下:
1. 将所有的“#define”删除,并展开所有的宏定义。
2. 处理所有条件预编译指令,比如“#if、#ifdef、#elif、#else、#endif”等。
3. 处理“#include”预编译指令,将被包含的文件插入到该预编译指令的位置。注意,这个过程是递归进行的,也就是说被包含的文件可能还包含其他文件。
4. 删除所有的注释/**/、//。
5. 添加行号和文件名标识。比如# 2 "hello.c" 2。以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号。
6. 保留所有的#pragma编译器指令,因为编译器需要使用它。
经过预编译后的.i文件不包含任何宏定义,因为所有的宏已经被展开,并且包含的文件也已经被插入到.i文件中。所以当我们无法判断宏定义是否正确或头文件是否包含正确时,可以查看预编译后的文件来确定问题。
编译
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生成相应的汇编代码文件,这个过程往往是我们所说的整个程序构建的核心部分,也是最复杂的部分之一。
现在版本的gcc把预编译和编译两个步骤合成一个步骤,使用一个叫做cc1的程序来完成这两个步骤。
对于C语言来说,这个预编译和编译程序是cc1,对于c++来说,这个程序是cc1plus,Java是jc1。所以,实际上gcc这个命令指示这些后台程序的包装,它会根据不同的参数要求去调用相应的预编译编译程序cc1、汇编器as、链接器ld。
汇编
汇编器是将代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。所以汇编器的汇编过程相对于编译器来讲比较简单,它没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编指令和机器指令的对照表一一翻译就可以了,“汇编”这个名字也来源于此。上面的汇编过程我们可以使用汇编器as来完成。
链接
链接是一个难点,怎么链接,链接过程到底做了什么,都是挥之不去的疑惑!
可以看到,我们需要将一大堆文件链接起来才可以得到“a.out”,即最终的可执行文件。其中-lgcc -lgcc_eh –lc这些都是什么参数?crt1.o、crti.o、crtbeginT.o、crtend.o、crtn.o这些文件是什么?为何要将它们和hello.o连接起来才可以得到可执行文件?请看链接一文。。。
摘抄自《程序员的自我修养》