使用gcc进行编译c语言文件很简单:
gabriel@gabriel-laptop:~$ gcc hello.c -o hello
但是事实上,这个过程可分为4步,分别是预处理(Prepressing),编译(Compilation),汇编(Assembly)和链接(Linking),下面分别简述以下这四个过程,并辅以实例给大家以感性认识。
预编译
预编译过程主要是处理源文件中#开头的预编译指令,主要规则如下
- 将所有的#define删除,并且展开所有的宏定义
- 处理所有的条件编译指令:#if #ifdef #elif #else #endif
- 处理#include,将被包含的文件插入到该预编译指令的位置
- 删除所有的注释//和/* */
- 添加行号和文件名标识,便于编译时产生调试用的行号信息
- 保留所有的#pragma,因为编译器需要
下面就只预编译一个简单的程序看看,这里只使用#include引入自己定义的头文件。
头文件hello.h如下:
主文件hello.c如下:
.c文件经过预编译之后将变成.i文件 .cpp文件经过预编译之后将会变为.ii文件
预编译可使用如下指令,其中gcc中-E选项代表只进行预编译
gabriel@gabriel-laptop:~$ gcc -E hello.c -o hello.i
或
gabriel@gabriel-laptop:~$ cpp hello.c > hello.i
查看文件hello.i
- 头文件hello.h已经被包含进来,在hello.i的第五行显示了hello.h里面的内容;
- 而hello.h前前后后带#加数字的行则是告诉编译器这里是源文件hello.c中的哪一行;
- 再来对照hello.c看,其中条件编译语句已经被删去,取而代之的是条件编译语句中编译条件成立的那一句;
- 另外,同样是在hello.c中的预处理#define HELLO 3在hello.c中已经不见了,并且main()函数中使用HELLO定义的数组a也被3代替;
- 最后,原本在第八行的注释在预编译后的文件中也不再显示了。
这样之后预编译的过程就算结束了
编译
编译的过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析及优化后生产相应的汇编文件代码(属于编译原理的内容)
编译之后将生成.s文件,即汇编代码的文件,继续使用上一步预编译完成的hello.i文件进行编译,使用如下命令,其中-S选项代表进行到编译
gabriel@gabriel-laptop:~$ gcc -S hello.i -o hello.s
同样,也可以直接从源文件进行预处理和编译
gabriel@gabriel-laptop:~$ gcc -S hello.c -o hello.s
另外,gcc专门有一个程序是完成以上编译加汇编两个步骤的,是位于/usr/lib/gcc/i486-linux-gnu/4.4/下的cc1(对于C++程序来说是cc1plus)
gabriel@gabriel-laptop:~$ /usr/lib/gcc/i486-linux-gnu/4.4/cc1 hello.c
可以看一下汇编出来的代码hello.s
汇编代码就不过多解释了,呵呵
汇编
汇编器是将汇编代码转变为机器可以执行的指令,也就是机器代码,由于每一个汇编语句几乎都对应一条机器指令,所以相对编译来说比较简单
汇编之后生成的是目标文件.o,这已经是机器代码的文件了。继续使用上一步编译之后的hello.s文件进行汇编,使用如下指令,其中-c代表汇编
gabriel@gabriel-laptop:~$ gcc -c hello.s -o hello.o
或者直接使用汇编器as
gabriel@gabriel-laptop:~$ as hello.s -o hello.o
若要从源文件直接获得目标文件,则可以
gabriel@gabriel-laptop:~$ gcc -c hello.c -o hello.o
链接
最后是链接,链接是一个非常复杂的过程,虽然目标文件已经是机器代码了,但是仍要通过各种链接才能最终变成可执行文件,linux下的链接器是ld。本书前半部分大部分篇幅都会讨论链接,所以具体内容后文将给出。
总结一下
一个c语言文件从源码文件编译链接成为可执行文件的整个过程如下所示
.c -> 预编译 -> .i -> 编译(cc1) -> .s -> 汇编(as) -> .o -> 链接(ld) -> 可执行文件