Appearance
第2章:指令、数据表示与函数调用
程序可见状态
一条普通指令可看成架构状态到架构状态的转换:读取寄存器或内存,计算结果,更新目的位置和 PC。异常会把控制交给指定处理入口,不能简单理解为普通函数返回。
固定宽度整数运算需要说明溢出规则。无符号 bit 加法在低 bit 上等价于模 运算,但源语言可能对有符号溢出规定不同。编译器依据源语言语义优化,不能把硬件行为直接当作 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)这里假设 a0、a1、a2 分别保存数组首地址、索引和增量。该序列不是原子加法;多线程同时执行可能丢失更新。指令条数少也不代表访问内存的等待自动消失。
调用约定与栈帧
函数调用要传递参数、保存返回地址、分配局部存储,并按约定保护寄存器。调用者保存寄存器由调用方在需要时保存;被调用者保存寄存器若被修改,应由被调用方恢复。
递归需要每次调用的返回地址和局部状态能分别保存。栈通常完成此任务,但函数可以不建立栈帧,例如不调用其他函数、局部状态能放寄存器的叶函数。栈帧属于编译器与 ABI 的组织方式,不是每执行一条指令就新建一个。
地址与数据布局
字节序规定多字节值如何排列在内存中,不改变该值在算术中的数学含义。对齐有助于简化硬件访问,有些 ISA 或具体指令会限制未对齐访问。
结构体布局还可能包含填充。把一个进程中的结构体内存直接发送给不同平台,可能遇到字节序、填充、指针值和类型宽度差异,因此通信格式需要独立定义。
练习
- 指出以上代码中哪些指令存在数据依赖。
a[i] += x为什么不能凭借一条add就被视为原子操作?- 递归函数的两个活动调用是否共享同一份局部变量存储?说明栈帧的作用。