Skip to content

第2章:指令、数据表示与函数调用

程序可见状态

一条普通指令可看成架构状态到架构状态的转换:读取寄存器或内存,计算结果,更新目的位置和 PC。异常会把控制交给指定处理入口,不能简单理解为普通函数返回。

固定宽度整数运算需要说明溢出规则。无符号 ww bit 加法在低 ww bit 上等价于模 2w2^w 运算,但源语言可能对有符号溢出规定不同。编译器依据源语言语义优化,不能把硬件行为直接当作 C 语言所有表达式的定义。

浮点表示则把有限位分给符号、指数和尾数。它扩大动态范围,却不能精确表示所有实数;例如结合律通常不成立。并行归约改变求和顺序,可能改变结果的低位。

load/store 结构

在典型 RISC-V 风格机器上,算术指令主要操作寄存器,load/store 负责与内存交换。表达式 a[i] += x,假设元素为 64 bit、地址有效且自然对齐,可分解为:

asm
slli t0, a1, 3     # i * 8
add  t0, a0, t0    # &a[i]
ld   t1, 0(t0)
add  t1, t1, a2
sd   t1, 0(t0)

这里假设 a0a1a2 分别保存数组首地址、索引和增量。该序列不是原子加法;多线程同时执行可能丢失更新。指令条数少也不代表访问内存的等待自动消失。

调用约定与栈帧

函数调用要传递参数、保存返回地址、分配局部存储,并按约定保护寄存器。调用者保存寄存器由调用方在需要时保存;被调用者保存寄存器若被修改,应由被调用方恢复。

递归需要每次调用的返回地址和局部状态能分别保存。栈通常完成此任务,但函数可以不建立栈帧,例如不调用其他函数、局部状态能放寄存器的叶函数。栈帧属于编译器与 ABI 的组织方式,不是每执行一条指令就新建一个。

地址与数据布局

字节序规定多字节值如何排列在内存中,不改变该值在算术中的数学含义。对齐有助于简化硬件访问,有些 ISA 或具体指令会限制未对齐访问。

结构体布局还可能包含填充。把一个进程中的结构体内存直接发送给不同平台,可能遇到字节序、填充、指针值和类型宽度差异,因此通信格式需要独立定义。

练习

  1. 指出以上代码中哪些指令存在数据依赖。
  2. a[i] += x 为什么不能凭借一条 add 就被视为原子操作?
  3. 递归函数的两个活动调用是否共享同一份局部变量存储?说明栈帧的作用。

上次更新: