CS110 计算机体系结构 5 C.A.L.L.

aaaaa Lv4

Snipaste_2026-07-20_14-57-27

C.A.L.L,是编译 Compilation - 汇编 Assembly - 链接 Linking - 加载 Loading 的缩写,是从(编译执行类语言)程序代码到进程的一般过程。我们接下来一个个部分来看。


编译 Compilation

对于 C 语言而言,编译过程由 C 编译器(如 gcc)完成。在这个过程中,程序员把写好的代码文件 main.c 交给编译器,然后编译器负责把这份代码编译为汇编代码文件 main.s,其中存放的是汇编语言代码,如 RISC-V。

既然是负责把高级语言转换为汇编语言,编译器就需要恪尽职守地努力”优化“转换生成的汇编语言,使其执行需要的时空开销尽可能小。我们在使用 gcc 进行编译时,可以选择 -O2 -O3 这样的优化策略——这些策略就是不同激进程度的优化。优化有很多种类型,在此仅举一个例子来供读者理解:

1
2
3
4
5
6
7
int sum(int n) {
int res = 0;
for (int i = 0; i < n; i++) {
res += i;
}
return res;
}

对于以上函数,如果按照 -O0 的忠于源码的优化方式来编译,编译结束后的汇编代码可能是这样的(事实上,gcc 的翻译结果并不是 RISC-V,在此仅作演示,方便理解):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
sum:
addi sp, sp, -16 # 开辟栈空间
sw s0, 12(sp) # 保存寄存器
addi s0, sp, 16
sw a0, -12(s0) # 将参数 n 存入栈 (内存)
sw zero, -8(s0) # res = 0
sw zero, -4(s0) # i = 0
.L2:
lw a4, -4(s0) # 加载 i
lw a5, -12(s0) # 加载 n
blt a4, a5, .L3 # 如果 i < n,跳转到循环体
j .L4 # 否则退出循环
.L3:
lw a4, -8(s0) # 加载 res
lw a5, -4(s0) # 加载 i
add a5, a4, a5 # res += i
sw a5, -8(s0) # 存回 res
lw a5, -4(s0) # 加载 i
addi a5, a5, 1 # i++
sw a5, -4(s0) # 存回 i
j .L2
.L4:
lw a0, -8(s0) # 返回 res
lw s0, 12(sp)
addi sp, sp, 16
ret

如果我们按照 -O3 的激进的优化方式来编译,结果可能会变成类似于这样:

1
2
3
4
5
6
7
8
9
sum:
blez a0, .L1 # 如果 n <= 0,直接跳转返回 0
addi a1, a0, -1 # a1 = n - 1
mul a0, a0, a1 # n * (n - 1)
srli a0, a0, 1 # n * (n - 1) / 2
ret
.L1:
li a0, 0 # 返回 0
ret

可以看到,最直观的区别就是,-O3 优化的汇编代码比 -O0 的汇编代码短得多。事实上,-O3 优化在这里避免了不必要的内存存取,改在寄存器中更快地完成这部分工作;甚至,这种优化还将庞大的循环直接从数学上化简成了求和公式。-O3 优化,在代码的时空复杂度上讲,比 -O0 优化有很大的缩减。

以上只是一个非常浅显的、便于理解的优化的例子。在后续的章节中,我们可能会涉及到如循环展开之类的优化方式,等涉及到时再补充。


汇编 Assembly

汇编过程是由汇编器 Assembler 完成的,它把由编译器生成的汇编代码汇编成二进制代码——这个过程的重点部分,我们已经在前两章中讨论过了,在此概括、补充一下完整过程:

第一步,根据含有伪指令的汇编代码来组织代码和数据,如将伪指令替换为标准指令、确定代码段、数据段的位置,声明全局符号等;第二步,汇编器将处理后的标准指令转换成二进制01串,生成机器语言;第三步,汇编器将生成的机器语言、信息表等打包为目标对象文件 .o,供后续链接器使用。

大端序 / 小端序

除了前面涉及的 RISC-V 代码翻译问题外,这里需要额外补充的是关于大端序/小端序的内容。所谓大端序/小端序 Big/Little Endian,就是缓冲区、内存、硬盘等存储容器中字节的存放/读取顺序。不难看出,这个顺序对于读取一段数据或代码来说至关重要。其中,小端序指的是最低位在最右侧的写法,大端序指的是最低位在最左侧的写法。RISC-V 使用的是小端序。比如,对于 0x12345678 这样一段十六进制数据,不同方式的存储如下:

内存地址 0x00 0x01 0x02 0x03
小端序 0x78 0x56 0x34 0x12
大端序 0x12 0x34 0x56 0x78

需要注意的是,具体按照小端序还是大端序存储/读取只是一种解释规则,具体顺序由 CPU 管理。另外,不论是小端序还是大端序,在每个字节内部的存储顺序是不会改变的——这是在说,即使是小端序,0x78 在同一内存地址字节上的存储方式还是 0b 0111 1000,而不会发生任何翻转。

汇编器的输出

这里简单整理一下汇编器的输出内容,主要包括代码和信息表两类,有兴趣的读者可以看一眼。

  • 目标文件头 Object file header 包含目标文件中其它部分的大小和位置信息
  • 文本段 Text segment 存储可执行的机器码
  • 静态数据段 Static data segment 存储静态数据
  • 符号表 Symbol table 列出文件中的标签和静态数据,方便被其他程序引用
  • 重定向表 Relocation table 包含之后需要被链接器 Linker 修正的代码
  • 调试信息 Debugging info 用于调试程序,记录变量名、函数名、行号与机器代码的对应关系

链接 Linking

【施工中】


回到目录

  • 标题: CS110 计算机体系结构 5 C.A.L.L.
  • 作者: aaaaa
  • 创建于 : 2026-08-13 22:00:00
  • 更新于 : 2026-08-13 22:43:27
  • 链接: https://redefine.ohevan.com/2026/08/13/零基础速通系列/CS110 计算机体系结构/零基础速通:CS110_计算机体系结构_5/
  • 版权声明: 版权所有 © aaaaa,禁止转载。