编译过程

编译过程包括预处理器、编译器、汇编器和链接器。
C 语言代码最终成为机器可执行的程序,会像流水线上的产品一样接受各项处理:

  1. 预处理器: 将c源程序main.c(.c)文件翻译成ASCII码的中间文件main.i(.i)。
  2. 编译器: 将main.c文件翻译成一个ASCII汇编语言文件main.s(.s)。
  3. 汇编器: 将main.s文件翻译成可重定向目标文件main.o(.o)文件。
  4. 链接器: 驱动程序经过相同的过程会生成多个.o文件,如sum.o文件,链接器会将main.o和sum.o文件以及一些必要的系统目标文件中组合起来,创建一个可执行目标文件。
  5. 加载器: 将可执行程序加载到内存并进行执行。

源文件的编译过程包含两个主要阶段:
第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。主要是以下几方面的处理:

  • 宏定义指令,如 #define a b 这种伪指令,预编译所要做的是将程序中的所有 a 用 b 替换,但作为字符串常量的 a 则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换
  • 条件编译指令,如 #ifdef, #ifndef, #else, #elif, #endif等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉
  • 头文件包含指令,如 #include “FileName” 。该指令将头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理
  • 特殊符号,预编译程序可以识别一些特殊的符号。例如在源程序中出现的 LINE 标识将被解释为当前行号(十进制数),FILE 则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换

第二个阶段编译、优化阶段,编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
汇编实际上指汇编器(as)把汇编语言代码翻译成目标机器指令的过程。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:

  • 代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写
  • 数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的

链接器任务

  • 符号解析: 我们在代码中会声明变量及函数,之后会调用变量及函数,所有的符号声明都会被保存在符号表(symbol table)中,而符号表会保存在由汇编器生成的 object 文件中(也就是 .o 文件)。符号表实际上是一个结构体数组,每一个元素包含名称、大小和符号的位置。在 symbol resolution 阶段,链接器会给每个符号应用一个唯一的符号定义,用作寻找对应符号的标志。
  • 重定位: 这一步所做的工作是把原先分开的代码和数据片段汇总成一个文件,会把原先在 .o 文件中的相对位置转换成在可执行程序的绝对位置,并且据此更新对应的引用符号(才能找到新的位置)

目标文件

  • 可重定位目标文件(.o文件):每个 .o 文件都是由对应的 .c 文件通过编译器和汇编器生成,包含代码和数据,可以与其他可重定位目标文件合并创建一个可执行或共享的目标文件。
  • 可执行目标文件(.out文件):由链接器生成,可以直接通过加载器加载到内存中充当进程执行的文件,包含代码和数据。
  • 共享目标文件(.so文件):在 windows 中被称为 Dynamic Link Libraries(DLLs),是类特殊的可重定位目标文件,可以在链接(静态共享库)时加入目标文件或加载时或运行时(动态共享库)被动态的加载到内存并执行。

可重定位目标文件

计算机系统——链接
重点掌握:

  • .data: 已初始化的全局和静态变量。局部变量运行时被保存在栈中,既不出现在.data中,也不出现在.bss中。
  • .bss: 未初始化的全局和静态变量,和所有初始化为0 的全局或静态变量。
  • .symtab: 符号表,存放程序中定义和引用的函数和全局变量的信息。

符号和符号表

在链接器的上下文中,有三种不同的符号:

  • 能被本模块和其他模块引用的全局符号。对应于非静态的函数和全局变量。
  • 由其它模块定义的但是可被本模块引用的全局符号。对应于其他模块中定义的非静态函数和全局变量。
  • 只能被本模块定义和引用的局部符号。对应于带static的函数和全局变量。这些符号在本模块的任何地方可见,但是不能被其他模块引用。
    .symtab中的符号表不包括对应于本地非静态变量的任何符号(没有static的局部变量),这些局部变量在运行时在栈中被管理,链接器对此类符号不感兴趣。静态本地变量不是在栈中管理的,在.data和。bss中编译器为这些变量分配空间,并在符号表中创建一个有唯一名字的本地链接器符号。

符号解析

链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定符号定义关联起来。

链接器解析多重定义的全局符号

当多个模块中定义同一个全局变量时如何解决?
首先知道符号的强弱之分:强符号:函数和初始化的全局变量。弱符号:未初始化的全局变量。
链接器在处理强弱符号的时候遵守以下规则:

  • 不能出现多个同名的强符号,不然就会出现链接错误
  • 如果有同名的强符号和弱符号,选择强符号,也就意味着弱符号是无效的
  • 如果有多个弱符号,随便选择一个
    以上这些bug在实际中并不是每次都能发现的,可能会产生更加严重的后果,因此如果可能,尽量避免使用全局变量。如果无法避免,应该注意以下几点:
  • 使用静态变量
  • 定义全局变量时初始化
  • 注意使用extern关键字

重定位

重定位就是把不同可重定位对象文件拼成可执行对象文件,具体来说就是合并输入模块,并为每个符号分配运行时地址。重定位由两步组成:

  • 重定位节和符号定义:合并不同输入模块中的同一类型节,如把不同模块中的.data合并成一个节,这个节成为输出文件的.data节。然后链接器为每个节和符号,赋予运行内存地址,这样程序中每条指令和全局变量就都有唯一的运行时的内存地址。
  • 重定位节中的符号引用:链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。

重定位条目

汇编器生成的可重定位目标文件不知道数据代码,引用的外部函数或变量在什么位置,这时汇编器就会为这些未知位置的目标引用生成一个重定位条目,根据该结构,链接器合并目标文件时就会修改这些未知位置的引用。

相关文章:

  • 2021-09-29
  • 2022-12-23
  • 2021-08-05
  • 2021-05-09
  • 2021-09-18
  • 2021-11-23
  • 2021-11-17
  • 2022-12-23
猜你喜欢
  • 2022-01-18
  • 2021-06-28
  • 2021-09-19
  • 2021-07-20
  • 2021-11-17
  • 2021-04-20
  • 2021-07-10
相关资源
相似解决方案