01_编译的详细过程
我们这里虽然介绍的是c程序的编译过程,但是实际上所有编译型语言的编译过程,大致是类似的
编译的四个过程
我们平时编译时,不管是通过IDE图形界面来编译的,还是通过命令行来编译的,我们感觉编译一下就完成了,然后就得到了你要的针对某OS和某CPU的二进制可执行文件
(机器指令的文件),但是实际上中间隐藏了四个过程,这四个过程被默默的处理了。编译四个过程:预处理、编译、汇编、链接
四个过程中的“编译”,特指其中的某个过程,这四个过程合在一起,我们也统称为编译,所以“编译”二字到底指的是第二个过程,还是全部过程的统称,这个就要看说话的“语境”了。其实统称的“编译”,完整的称法应该叫“编译链接”,只是简称为编译而已。
如果这四个过程是一次性编译完成的,这个四个过程分别会产生相应的文件,只不过中间产生的文件都是过渡性的临时文件,使用完成后就会被删除。
第1步:预编译(预处理)
之所以叫预编译,表示为正式的编译做准备,预编译也被称为预处理。
-
(1)
**.c
—————预处理(预编译)—————>**.i
如果编译过程是一次性完成的话,.i文件只是一个过渡性文件,.i被称为扩展后的c源码文件。
为什么还叫c源码文件呢?
因为预处理后,只是宏定义等东西不见了,但是C源码依然还在,比如main函数,各种自己写的子函数,依然存在,所以还是被称为c源码文件。打开.i文件后,我们是能够看的懂的,所以.i文件是ascii文件。后面会演示给大家看。 -
(2)预编译是以单个文件为单位来进行的
a.c ——————> a.i
b.c ——————> b.i
当然**.i的这个名字并不是固定的。 -
(3)预处理做了什么处理
- 1)宏替换
将宏替换为真实的宏体, 比如程序性中有使用NUM这个宏,这个宏的定义为#define NUM 100,程序中所有的NUM都会被替换为100。 - 2)包含头文件
将include包含的.h头文件内容,全部复制到.c文件中,因为头文件中定义了类型、宏、函数声明等等,
这些都是函数调用会用到的,你调用某个函数时,就必须包含这个函数要的头文件。
疑问:头文件那么多内容,都包含进去的话,不会太多了吗?
编译时,编译器只使用要用的东西,用完后包含的内容都会被丢弃,实际上并不占空间。 - 3)条件编译
处理#if #endif 这类的东西 - 4)处理一些特殊的预处理关键字
有关预处理的宏定义、头文件包含、条件编译、特殊预处理关键字,会在后面专门的《预处理》章节中讲到。
- 1)宏替换
第2步:编译
-
(1)
***.i
———编译———>***.s
.s:汇编文件 -
(2)同样也是以单个文件为单位来进行的
-
(3)编译做了什么
将c语法的c源码,翻译为汇编语法的汇编源码。 -
(4).s是ascii码文件
因为汇编也是人能看懂的文字编码形式,所以.s汇编文件也是ASCII码文件。
第3步:汇编
-
(1)
***.s
————汇编————>***.o
.o文件是纯二进制文件
因为.o中放的是纯二进制的机器指令,所以我们打开后看不懂。 -
(2)同样也是以文件为单位来进行的
-
(3)汇编做什么
将ASCII的汇编源码,翻译为能够被CPU执行的机器指令,.o文件中放的就是机器指令。
但是.o文件还无法运行,需要链接后才能运行。
第4步:链接
***1.o ——————————\<br/>
\<br/>
***2.o ____________\ a.out(可执行文件)<br/>
/<br/>
.... /<br/>
/<br/>
***n.o —————————/<br/>
-
(1)链接(连接)做了什么
- 1)将众多的.o合成一个完整的可执行文件
.o之间相互依赖的,比如a.o中调用的函数,被定义在了b.o中,如果不链接在一起的话,是无法工作的。 - 2)链接时,需要加入额外的启动代码
这个启动代码并不是我们自己写的,main函数是由启动代码调用的,我们的程序是从启动代码开始运行的,后面会介绍启动代码是怎么来的。 - 3)链接为一个可执行文件时,需要进行符号解析和地址重定位
后面会介绍什么事符号解析和地址重定位。
- 1)将众多的.o合成一个完整的可执行文件
-
(2)Linux下可执行文件命名问题
在windows下,可执行的尾缀时.exe,但是在Linux下,可执行文件没有固定的尾缀。 -
(3)如果整个C工程就一个.c,最后得到的只有一个.o,此时还需要链接吗,可不可以直接执行呢?
同样的要链接后才能运行,因为链接后才有启动代码和重定位后的地址,否者无法运行。