程序编译过程

程序编译过程


version : v1.0 「2022.7.28」 最后补充

author: Y.Z.T.


简介: 简单程序的编译过程





1️⃣ 编译流程

程序的整个编译流程大致分成几个阶段:

  • 预处理 : 将预处理指令进行处理 , 预处理器将源文件(.c) 经过预处理变成 文件(.i )
  • 编译 : 编译器调用解析工具 , 将预处理后的源文件( .i )编译成汇编文件( .s)
  • 汇编 : 这是也是编译的第二阶段 , 通过汇编器将汇编文件( .s) 汇编成可重定位的目标文件( .o)
  • 链接 : 将各个目标文件( .o)链接成可执行文件( 也是可执行文件的一种 )

程序编译 , 链接流程图:

image-20220928113639502



2️⃣ 可执行文件

一个可执行文件通常由一系列不同的段(section)构成:代码段数据段BSS段只读数据段等。

C语言到可执行文件 :

image-20220928114314496

  • 函数 翻译成二进制指令放在 代码段
  • 初始化的全局变量静态局部变量 放在 数据段 中(.data)
  • 未初始化的全局变量静态变量 放在 BSS段 中(.bss)
  • 程序中定义的一些字符串 , printf函数打印的字符串常量放在 只读数据段( .rodata)


3️⃣ 预处理

预处理过程 就是 在编译源程序之前 , 先处理源文件中的各种预处理指令


预处理主要包括以下操作 :

  • 头文件展开:#include包含的头文件内容展开到当前位置 , 并删除#include
  • 宏展开: 展开所有的宏定义,并删除#define
  • 条件编译: 根据宏定义条件,选择要参与编译的分支代码,其余的分支丢弃。
  • 删除注释
  • 添加行号和文件名标识: 编译过程中根据需要可以显示这些信息。
  • 保留#pragma命令: 该命令会在程序编译时指示编译器执行一些特定行为。


4️⃣ 编译

汇编过程主要包括以下步骤 :

  • 词法分析

  • 语法分析

  • 语义分析

  • 中间代码生成

  • 汇编代码生成

  • 目标代码生成



4.1 词法分析

词法分析主要用来解析C程序语句 , 词法分析一般会通过词法扫描器从左到右 , 将源程序分解为一系列不能再分解的记号单元–token。


常见token

  • C语言的各种 关键字 : int,floatfor,whilebreak等。
  • 用户定义的各种 标识符 : 函数名变量名标号等。
  • 字面量: 数字字符串等。
  • 运算符: C语言标准定义的40多个运算符。
  • 分隔符: 程序结束符分号、for循环中的等

示例:

1
sum = a + b / c;

如上所示:

  • 经过词法分析后 分解成 sum ,=, a ,+, b, /, c,; 八个token
  • 如果程序出现中文符号圆角\半角字符 等 ,程序就会在这个阶段发错编译错误


4.2 语法分析

语法分析主要是对前一阶段产生的token序列进行解析, 看是否能构建成一个语法上正确的语法短语(程序、语句、表达式等)。


说明:

  • 词法分析语法分析工具在对token序列分析过程中, 如果发现不能构建语法上正确的语句或表达式,就会报语法错误: syntax error
  • 如果程序语句后 少了 **结束分号 ** 或 循环中少了分号 ,就会在此阶段产生编译错误


4.3 语义分析

语义分析主要对语法分析输出的各种表达式、语句进行检查,看看有没有错误。


例如 :

  • 传递给函数的实参与函数声明的形参类型不匹配,
  • 使用了一个未声明的变量
  • 除数为零了;
  • break在循环语句或switch语句之外出现了,
  • 在循环语句之外发现了continue语句等


4.4 生成中间代码

说明:

  • 中间代码是一维线性结构 , 类似伪代码
  • 通过中间代码 , 可以很容易的将中间代码翻译成汇编代码

示例:

1
2
3
4
5
6
7
8
9
int main(void)
{
int sum = 0;
int a = 2;
int b = 1;
int c = 1;
sum = a + b / c;
return 0;
}

转换为中间代码 三地址码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
main ()
{
int D.4227;
int D.4228;

{
int sum;
int a;
int b;
int c;

sum = 0;
a= 2;
b= 1;
C= 1;
D.4227 = b / c;
sum =D.4227 + a;
D.4228 = 9;
return D.4228;
}
D.4228 = 0;
return D.4228;
}

中间代码转换为 汇编代码 :

1
2
3
4
5
MOV R0, #2		
MOV R1, #1
MOV R2, #1
DIV R3, R1, R2 ; R3 = R1 / R1
ADD R0 RO.R3 ; R0 = R0 + R3
  • 将变量变量a、b、c分别放到寄存器R0、R1、R2中,
  • 临时变量D.4427使用R3代替,然后使用ADD命令完成累加。


4.5 汇编过程

  • 汇编器主要是 将汇编代码翻译成对应的二进制指令;
  • 同时生成一些必要的信息 , 以section的形式组装到目标文件中

汇编过程:

image-20220928233023855



5️⃣ 链接过程

  • 编译器在编译一个项目时,是以C源文件为单位进行编译的,每一个源文件编译生成一个对应的目标文件(.o)
  • 但这些单独的目标文件(.o)是不可执行的 , 属于可重定位的目标文件;
  • 它们要经过链接器 重定位链接 之后,才能组装成一个可执行的目标文件a.out。
  • 链接器将各个目标文件组装在一起后, 重新 修改 各个目标文件中的变量或函数的 地址 ,这个过程一般称为 重定位
  • 链接过程中 , 将各个目标文件分段组装 ; 例如 : 将各个目标文件的代码段放在一起,作为最终生成的可执行文件的代码段; 将各个目标文件的数据段放在一起,作为可执行文件的数据段。

-------------已经到底啦! -------------