C/C++ 程序编译与链接的过程详解(静态链接)

我们知道一个程序的执行需要经过编译和链接两个阶段,其过程究竟是怎样的呢?

程序的编译阶段分为以下几个步骤,分别是预编译、编译、汇编、生成二进制可重定向文件(.o)。

  1. 预编译: 首先是源代码文件xxx.c和相关的头文件被预编译器编译成一个.i文件。对于C++程序来说,源代码的扩展名可能是.cpp或.cxx,头文件的扩展名是.hpp,而编译后的文件扩展名是.ii。
    第一步的预编译过程相当于如下指令:
    gcc -E hello.c -o hello.i 或 cpp hello.c > hello.i.
    预编译过程做的事情:处理所有以#开头的预编译指令,删除注释,添加行号和文件名标识,保留所有的#pragma编译器指令(因为编译器要使用它们)。

  2. 编译:进行语法分析、词法分析和语义分析,并且将代码优化后产生相应的汇编代码文件(ASCLL文件),即.s 文件。这个过程是整个程序构建的核心部分,也是最复杂的部分之一。
    相当于如下指令:
    gcc -S hello.i -o hello.s

  3. 汇编:通过不同平台(Windows、Linux)的汇编器将汇编代码翻译成机器码,即生成二进制可重定向文件(.o)。
    相当于指令:
    as hello.s -o hello.o 或 gcc -c hello.s -o hello.o
    或者使用gcc命令将源文件一部生成目标文件:
    gcc -c hello.c -o hello.o
    何为二进制可重定向文件(.o)?为什么是可重定向文件?
    二进制可重定向文件包含二进制代码和数据,其形式可以在链接时与其他二进制可重定向文件合并起来,由链接器产生一个可执行目标文件。
    一个典型的二进制可重定向文件格式如下图:

    C/C++ 程序编译与链接的过程详解(静态链接)_第1张图片

    我自己编写了两个文件:
    mian.c

#include 

int sum(int, int);

int main.c
{
    int a = 10;
    int b = 20;
    printf("sum=%d\n", sum(a, b));

    return 0;
}

a.c

int sum(int x, int y)
{
    return x + y;
}

用命令
gcc -c main.c a.c 生成.o文件,并用命令objdump -t main.o去查看生成的符号表,如下图所示:

C/C++ 程序编译与链接的过程详解(静态链接)_第2张图片

那为什么是可重定向呢?
在编译阶段编译器和汇编器会生成每个文件的符号表,符号表中存放的是由程序产生的符号,比如函数名,变量名等,从上图可以看出,编译器没有给符号分配正确的地址(图中地址全为0000 0000),所以在代码段计算机指令无法找到相应的变量或函数的地址,因此,二进制可重定向文件是无法执行的。所以二进制可重定向文件得等到重定向以后才成为可执行文件。

程序的链接阶段可分为两个步骤:
第一步:链接器首先将多个.o 文件相应的段进行合并,建立映射关系并且去合并符号表。进行符号解析(符号解析的目的是让所有符号的引用找到该符号的定义,如上图中UND后面的符号),符号解析完成后就是给符号分配虚拟地址。

第二步:将分配好的虚拟地址与符号表中的定义的符号一一对应起来,使其成为正确的地址,是代码段的指令可以根据符号的地址执行相应的操作,最后由链接器生成可执行文件。

你可能感兴趣的:(操作系统)