GNU C 编译器如何工作

源代码到机器码程序

  • 预处理:预处理会将源文件和使用#include指令包含的所有头文件合成一个更大的输入文件。
  • 扫描和解析:根据语法规则描述,词法分析器逐字符分析源代码文本,查找程序设计语言的关键字。语法分析器获取由扫描器提供的输入流。在计算机内存中创建数据结构,以便作为源代码更抽象的表示,用于计算机的处理。
  • 中间代码生成:将词法分析器和语法分析器建立的语法分析树转换为另一种寄存器传输语音(register transfer language, RTL)。是C语言与汇编语言的过渡。
  • 优化:对以RTL语言编写的中间代码进行优化
  • 代码生成:对目标处理器生成实际的汇编代码,会产生一个汇编指令组成的文本文件,在通过其他程序二进制文件。

汇编和链接

上一节提到的其他程序,值得是汇编器和链接器。各个汇编语句被转换为依处理器类型而不同的专用二进制格式。将常量放到二进制程序代码中。Linux下通常使用ELF格式在二进制文件中保存程序代码和数据。

链接器必须调整汇编代码中的分支地址。除了通过符号名称进行引用之外,但二进制码必须指定相对或绝对的分支地址。如:跳到当前地址往后5个字节、跳到位置x。

过程调用

过程调用的基本术语

系统栈:是一个内存区,位于进程地址空间的末端。在将数据压栈时,栈自顶向下增长。如果调用了嵌套的过程,栈会自上而下增长,并接受新的活动记录,由标记顶部位置的栈指针和标记底部位置的栈指针定义。
zhd.png

栈的调用过程
  • 在栈中建立参数列表,传递被调用函数的所有参数到栈中
  • 调用call,将指令指针的当前值(call之后的下一条指令)压栈,代码的控制流转向被调用的函数。

被调用的过程负责管理帧指针,执行下列步骤:

  1. 将前一个栈指针压栈,因而栈指针下移
  2. 将栈指针的当前值赋值给栈指针,标记当前执行的函数的栈区的起始位置。
  3. 执行当前函数的代码。
  4. 在函数结束时,存储的原帧指针位于栈的地步。将其值弹出到栈指针寄存器,使之指向前一个函数的栈区起始位置。现在,对当前函数执行call指令时压栈的返回地址位于栈底。
  5. 调用return,将返回地址从栈弹出,处理器转移到返回地址,代码的控制流也返回到调用函数。

优化

  1. 常数简化:如int a=3+4优化为int a=7
  2. 循环优化
  3. 公共子表达式消除:将程序中所有的x+y替换成z=x+y
  4. 死代码消除:删除不会执行的代码,码农逻辑导致,比如删除if(false)分支

内联函数

编译时会作为宏定义一样将代码粘贴到使用了该内联函数的函数中

Last modification:September 1, 2022
If you think my article is useful to you, please feel free to appreciate