程序的编译、链接

来源:互联网 发布:使用ping命令测试网络 编辑:程序博客网 时间:2024/06/09 20:08

《程序员的自我修养》学习笔记。想要了解更多的朋友可以去看看此书(电子工业出版社)。

在Linux下,使用GCC来编译Hello World时,只使用最简单的命令

$gcc hello.c$./a.outHello World

但上述过程可以分解为4个步骤,即预处理、编译、汇编、链接
GCC编译过程分解


预处理/预编译

$gcc -E hello.c -o hello.i或者$cpp hello.c > hello.i

预编译过程主要处理源代码中以#开始的预编译命令。比如#include#define等,主要规则如下:

  • 将所有的“#define”删除,并且展开所有宏定义。
  • 处理所有条件预编译指令,比如#if#ifdef#elifelse#endif
  • 处理#include,将被包含的文件插入到该预编译指令的位置。该过程是递归进行的,被包含文件可能还包含其他文件。
  • 删除所有注释。
  • 添加行号和文件名标识,便于编译器产生调试用的行号信息及用于编译错误/警告时能够显示行号。
  • 保留所有#pragma,因为编译器要使用它们。

编译

$gcc -S hello.i -o hello.s

编译把预处理完的文件进行一系列词法分析、羽凡分析、语义分析及优化后生成相应的汇编文件代码。现在版本的GCC把两个步骤合并成一个,并使用一个叫cc1的程序来完成。

array[index] = (index+4) * (2+6)

以一段简单的C语言代码为例来描述从源代码(source code)最终目标代码(final target code)的过程。

首先代码被输入到扫描器(Scanner)。它只是简单地进行词法分析,将源代码的字符序列分割成一系列的记号(Token)。比如上面那行代码,经过扫描以后,产生了16个记号。词法分析产生的记号一般可以分为:关键字、标识符、字面量(包含数字、字符串等)和特殊符号(如加号、等号)。在识别记号的同时,扫描器也将标识符存放到符号表,将数字、字符串常量存放到文字表等。
有一个叫做lex的程序可以实现词法扫描。

接下来到语法分析器(Grammar Parser)。它采用上下文无关语法(Context-free Grammar)对扫描器产生的记号进行语法分析,产生语法树(Syntax Tree)。因为符号和数字不是由其他表达式组成的,所以他们通常作为树的叶子节点。在语法分析的同时,符号的优先级和含义(有些符号具有多重含义)也被确定下来。
语法分析可以用yacc(Yet Another Compiler Compiler)。由于可以对它改变语法规则来实现一个新的语法分析器,因此它又被称为“编译器编译器(Compiler Compiler)”

语义分析由语义分析器(Semantic Analyzer)完成。例如两个指针乘法在语法上是合法的,但却没有意义。
编译器所能分析的是静态语义(Static Semantic),即编译期可以确定的语义。(运行时才能确定的称为动态语义)。静态语义包括:声明类型匹配类型的转换。如果某些类型需要做隐式转换,语义分析器会在语法树中插入相应的结点。

现代编译器有很多层优化。比如源码级优化(Source Code Optimizer)会在源代码级别进行优化。比如上例中(2+6)这个表达式就可以被优化掉,因为它的值可以在编译期确定。优化后的语法树如下。
优化后的语法树


汇编

$as hello.s -o hello.o或者$gcc -c hello.s -o hello.o

汇编器将汇编代码转变成机器可以执行的命令。上面的汇编过程我们可以调用汇编器as来完成


链接

在编译过程中,变量的地址该如何确定?如果这些变量是定义在其他文件中的时候还如何处理?事实上,定义在其他模块的全局变量和函数在最终运行时的绝对地址都要在链接时才能确定
链接过程主要包括了地址和空间分配(Address and Storage Allocation)、符号决议(Symbol Resolution)重定位(Relocation)。目标文件(Object File)和库(Library)被链接器一起链接形成最终的可执行文件。