2017-09-18 23:06:23 +0000   |     c compile   |   Viewed times   |    

C语言编译过程主要分为4个步骤

4steps

  1. 预处理:即完成宏定义以及include文件展开工作。生成.i文件。命令gcc -E
  2. 编译成汇编代码:汇编代码的后缀使.s。命令gcc -S
  3. 进一步编译成目标文件:目标文件就已经二进制文件了,后缀.o。命令gcc -C
  4. 链接:把一个个单独的目标文件,以及系统库文件合并生成最终的可执行程序。命令gcc

粗略一点可以看成两个步骤

这里面第一步预处理就是简单的宏替换,第二步汇编就是简单的语法转换。所以前三步,可以简单地看成从源代码->二进制目标文件的编译过程。所以C语言的遍历,主要就是2步,

  1. 单个源代码文件 -> 单个二进制目标文件
  2. 多个二进制目标文件 -> 链接成一个可执行文件

每个目标文件(Object File)都是一个独立的和外部无关的对象

每一个独立目标文件都可以看成是一个C语言的对象。所以K&R书上说:

C语言程序可以看成由一系列外部对象构成。

关键在于,

C语言每个目标文件形成了一个独立的编译单元。每个目标文件调用外部函数或者变量时,编译器都在 函数入口地址留空待定。所以编译的时候完全不依赖外部对象。

最后把函数入口地址填上的工作,是在 链接 这一步来做。

一篇相关博客的描述

对于C,首先要把源文件编译(Compile)成目标文件(Object File),也就是Windows下的 .obj 文件。然后再把单个或多个 Object File 合并成可执行文件,也就是Windows下是 .exe 文件,这个动作叫作链接(Link)。

编译时,编译器需要检查语法是否正确,函数、变量的声明是否正确;只有函数、变量的声明但没有定义是完全正确的。函数声明是告诉编译器该函数已经存在,但是入口地址还未确定,暂时在此做个标记,链接时编译器会找到函数入口地址,并将标记替换掉。

这些都校验通过,编译器就可以编译出中间目标文件。一般来说,编译是针对单个源文件的,多个源文件需要编译多次,每个源文件都会生成一个对应的目标文件。

编译产生的 .obj 文件已经是二进制文件,与 .exe 的组织形式类似,只是有些函数的入口地址还未找到,程序不能执行。链接的作用就是找到函数入口地址,将所有的源文件组织成一个可以执行的二进制文件。

链接时,主要是链接函数和全局变量。链接器并不管函数或变量所在的源文件,只管中间目标文件(Object File)。

总结一下:源文件首先会生成中间目标文件,再由中间目标文件生成可执行文件。在编译时,编译器只检测程序语法、函数声明、变量声明是否正确。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是 Link 2001 错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File。

知乎上Thomson的回答

[Thomson在知乎关于C语言编译和链接的解释] –> https://www.zhihu.com/question/26342083

在call指令的操作数上留一个整数的位置,让linker来填。这样也有坏处,因外可能这个相对寻址用不了一个整数(指针)大小,所以生成的代码不是最优的。 当然,现在obj里面都不存机器码了,存的是中间语言,所以这种也能优化。

良好的编译实践

所以假设我们有,

a.c
b.c
c.c

先把每个源文件编译成对应的目标文件,

gcc -c a.c -o a.o
gcc -c b.c -o b.o
gcc -c c.c -o c.o

这一步也可以简化为,

gcc -c a.c b.c c.c

编译器会自动生成a.o,b.o,c.o对象文件。

最后再把这几个目标文件链接成一个完整的可执行文件,

gcc -o run.exe a.o b.o c.o