一、预处理
? ? ? ?所谓的预处理就是解释源程序当中的所有的预处理指令,那些诸如#include、#define、#if 等以井号’#’开头的语句就是预处理指令。这些工作包括我们熟悉的诸如文件包含、宏定义、条件编译
等等。
gcc hello.c -o hello.i -E // -o 执行输出的文件名
加上一个编译选项 -E 就可以使得 GCC 在进行完第一阶段的预处理之后停下来,生成一个默认后缀名为.i 的文本文件。
二、编译
? ? ? ? 编译阶段是四个阶段中最为复杂的阶段,它包括词法和语法的分析,最终生成对应硬件平台的汇编语言(不同的处理器有不同的汇编格式),具体生成什么平台的汇编文件取决于所采用的编译器,如果用的是 GCC,那么将会生成 x86 格式的汇编文件,如果用的是针对 ARM 平台的交叉编器,那么将会生成 ARM 格式的汇编文件。
gcc hello.i -o hello.s -S
加上一个编译选项 -S 就可以使得 gcc 在进行完第一和第二阶段之后停下来,生成一个默认后缀名为.s 的文本文件。打开此文件看一看,你会发现这是一个符合 x86 汇编语言的源程序文件。
三、汇编
? ? ? ?编译器 gcc 将会调用汇编器 as 将汇编源程序翻译成为可重定位文件。汇编指令跟处理器直接运行的二进制指令流之间基本是一一对应的关系,该阶段只需要将.s 文件里面的汇编翻译成指令即可。
gcc hello.s -o hello.o -c
编译的时候加上一个编译选项-c,则会生成一个扩展名为.o 的文件,这个文件是一个 ELF 格式的可重定位(relocatable)文件,所谓的可重定位,指的是该文件虽然已经包含可以让处理器直接运行的指令流,但是程序中的所有的全局符号尚未定位,所谓的全局符号,就是指函数和全局变量,函数和全局变量默认情况下是可以被外部文件引用的,由于定义和调用可以出现在不同的文件当中,因此他们在编译的过程中需要确定其入口地址,比如 a.c 文件里面定义了一个函数 func( ),b.c 文件里面调用了该函数,那么在完成第三阶段汇编之后,b.o 文件里面的函数 func( )的地址将是 0,显然这是不能运行的,必须要找到 a.c 文件里面函数 func( )的确切的入口地址,然后将 b.c 中的“全局符号”func重新定位为这个地址,程序才能正确运行。
总结: 汇编之后的所有的全局变量以及函数的入口地址都被暂时设置为0 ,因此不能直接使用,需要下一个步骤, 链接
四、链接
? ? ? ? 经过汇编之后的可重定位文件不能直接运行,因为还有两个很重要的工作没完成,首先是重定位,其次是合并相同权限的段。关于重定位的问题,上面已经给出了简单的描述。更一般的地,我们编译一个程序通常都需要链接系统的标准 C 库、gcc 内置库等基本库文件。因为 Linux 下任何一个程序编译都需要用到这些基本库的全局符号。
gcc hello.o -o hello -lc -lgcc
标准 C 库和 gcc 内置库是如此的基本,因此-lc 和-lgcc 是默认的,可以省略。
|