8.4 CPU 专题:从 C 程序到机器指令

408 指令系统与 CPU 综合题的解法——C 循环与汇编代码的对应、指令格式与机器码、相对转移目标地址与偏移量(MIPS 与 x86 小端)、指令执行次数,以及流水线中的数据相关分析。

考查要求 ★★★ 重点掌握 循环的汇编结构R/I/J 型指令格式机器码的拼接相对寻址以下一条指令地址为基准x86 小端存放偏移量指令执行次数数据相关与阻塞周期

408 怎么考 CPU 与指令

指令系统和 CPU 的综合题通常给出一段 C 代码及其反汇编结果(每行包括指令地址、机器码、汇编指令),然后围绕它提问:

常见问法用到的知识
指令字长多少?某指令是什么格式?操作码、寄存器字段各几位?指令格式(第 4 章)
某条转移指令的目标地址 / 偏移量是多少?相对寻址,PC 的自增
某变量存放在哪个寄存器?数组元素的地址如何计算?机器级代码表示
循环共执行多少条指令?某条指令执行多少次?程序结构分析
流水线中哪些指令存在数据相关?要阻塞几个周期?指令流水线(第 5 章)
过程调用时栈帧如何变化?返回地址是多少?过程调用(第 4 章「程序的机器级代码表示」)

循环的机器级代码

for 循环编译后通常分为四部分:初始化(只执行一次)、条件判断(每轮一次,外加最后一次判断失败)、循环体和更新、跳回条件判断。以数组求和为例(MIPS,数组 a 的首地址在 $s0,n 在 $s1,sum 在 $s2,i 在 $t0):

C 代码对应的指令每次执行的条数
sum = 0; i = 0;两条 add1 次 × 2
i < nslt + beq(n + 1) 次 × 2
sum += a[i]sll(i × 4)、add(求 &a[i])、lw、addn 次 × 4
i++ 并回到开始addi、jn 次 × 2

总条数 = 2 + 2(n + 1) + 4n + 2n + 1(最后保存 sum 的 sw)= 8n + 5。注意数组下标要乘以元素大小(sll 左移 2 位即 × 4)才能得到字节地址。

下面的动画逐条执行这段程序。右侧显示当前指令的各字段,左侧最后一列统计每条指令的执行次数。修改数组 a 的内容和长度可以验证 8n + 5:

动画演示从 C 循环到机器指令:执行、译码与计数
地址    机器码   汇编指令           执行次数0040000000009020 add $s2, $zero, $zero0040000400004020 add $t0, $zero, $zero004000080111482Aloop: slt $t1, $t0, $s10040000C11200006 beq $t1, $zero, exit0040001000085080 sll $t2, $t0, 20040001401505020 add $t2, $t2, $s0004000188D4B0000 lw $t3, 0($t2)0040001C024B9020 add $s2, $s2, $t30040002021080001 addi $t0, $t0, 10040002408100002 j loop00400028AE720000exit: sw $s2, 0($s3)指令字段寄存器$s010010000H$s13$s20$s310010040H$t00$t10$t20$t30说明· 按字节编址,指令定长 4 字节,PC 每次加 4· beq 为相对寻址:目标 = (PC) + 4 + offset × 4· j 为伪直接寻址:目标 = (PC+4)[31:28] ‖ target ‖ 00· 机器码 = 各字段二进制拼接后写成十六进制已执行 0 条指令
当前指令转移成功
C 源程序
1int sum = 0;
2for (int i = 0; i < n; i++)
3 sum += a[i];
1/31开始执行:$s0 = &a[0] = 10010000H,$s1 = n = 3,$s3 = &sum。程序从 00400000H 开始,每条指令 4 字节

指令格式与机器码

MIPS 指令定长 32 位,只有三种格式:

格式字段(位数)用于
R 型op(6) | rs(5) | rt(5) | rd(5) | shamt(5) | funct(6)寄存器之间的运算:add、slt、sll…;op 全为 0,由 funct 区分具体操作
I 型op(6) | rs(5) | rt(5) | imm / offset(16)立即数运算 addi、访存 lw/sw(基址 + 偏移)、条件转移 beq/bne(相对寻址)
J 型op(6) | target(26)无条件跳转 j:目标 = (PC + 4) 的高 4 位 ‖ target ‖ 00

把各字段的二进制按顺序拼接起来,每 4 位写成一个十六进制数字,就是机器码。反过来,给出机器码也可以拆出各字段。

综合题(408 真题风格)

针对上面的程序:(1) 指令 beq $t1, $zero, exit 位于 0040000CH,exit 的地址为 00400028H,beq 的 offset 字段是多少?写出这条指令的机器码(beq 的 op = 4,$t1 为 9 号寄存器)。(2) j loop 的 target 字段是多少?(3) 若 n = 100,这段程序共执行多少条指令?其中 lw 执行多少次?(4) 第 3 行 slt 的机器码为 0111482AH,说出它的格式和各字段的值。

查看解答

(1) 执行 beq 时 PC 已指向下一条指令 00400010H。offset = (00400028H − 00400010H) / 4 = 18H / 4 = 6。机器码字段:op = 000100,rs = 01001($t1),rt = 00000($zero),offset = 0000 0000 0000 0110,拼接得 0001 0001 0010 0000 0000 0000 0000 0110 = 11200006H。

(2) loop 的地址 00400008H,去掉最低 2 位(右移 2 位)并保留低 26 位:target = 0100002H(26 位二进制 00 0001 0000 0000 0000 0000 0010),机器码 08100002H。

(3) 8n + 5 = 805 条;lw 只在循环体中,执行 100 次。

(4) 0111482AH = 000000 01000 10001 01001 00000 101010:op = 0,是 R 型;rs = 8($t0),rt = 17($s1),rd = 9($t1),shamt = 0,funct = 2AH(slt)。

相对转移的目标地址

相对寻址的基准是取指之后的 PC,也就是下一条指令的地址。不同指令系统的约定不同:

指令系统(PC) 的值偏移量的单位目标地址
MIPS(定长 4 B)本指令地址 + 4字(4 B)(PC) + offset × 4
x86(变长)本指令地址 + 本指令长度字节(PC) + 偏移量
按字编址、单字长指令本指令地址 + 1字(PC) + 偏移量

x86 是小端机器:多字节的偏移量在机器码中低位字节在前。例如 call 的机器码 E8 D1 FF FF FF 中,E8 是操作码,偏移量是 FFFFFFD1H = −47。

计算器相对转移:目标地址 ⇄ 偏移量
  1. 机器码中的偏移字节(按地址从低到高):D1 FF FF FF
  2. 小端方式:低地址存低位字节,因此偏移量 = FFFFFFD1H,按 32 位补码解释为 -47
  3. 取指后 PC 已指向下一条指令:(PC) = 0040102AH + 5 = 0040102FH
  4. 目标地址 = (PC) + 偏移量 = 0040102FH − 47(2FH)= 00401000H
相对寻址的基准是取指之后的 PC,即下一条指令的地址;偏移量是带符号数(补码),负数表示向前(低地址)转移,常见于循环。
例题(x86)

某 x86 程序的反汇编片段如下(按字节编址):

0040102A  E8 D1 FF FF FF      call  f1
0040102F  ...
00401084  7E 0F               jle   ...
00401086  ...

(1) call 指令调用的函数 f1 的首地址是多少?(2) jle 指令转移成功时的目标地址是多少?(3) 若在 00401095H 处用 2 字节的 jmp(操作码 EBH)跳回 00401060H,它的机器码是什么?

查看解答

(1) call 指令长 5 字节,(PC) = 0040102AH + 5 = 0040102FH;偏移量按小端读作 FFFFFFD1H = −47 = −2FH。f1 的首地址 = 0040102FH − 2FH = 00401000H。call 同时把返回地址 0040102FH 压栈。

(2) jle 长 2 字节,(PC) = 00401086H,偏移量 0FH = 15,目标 = 00401086H + 0FH = 00401095H。

(3) (PC) = 00401095H + 2 = 00401097H,偏移量 = 00401060H − 00401097H = −37H = −55,8 位补码为 C9H,机器码为 EB C9。

流水线中的数据相关

把上面的循环放到 IF、ID、EX、MEM、WB 五段流水线中执行,相邻的指令经常**写后读(RAW)**同一个寄存器。设寄存器堆”前半周期写、后半周期读”,则:

  • 不采用旁路时,结果在 WB 段写回。后一条指令与前一条相邻(距离 1)需阻塞 2 个周期,距离 2 需阻塞 1 个周期,距离 ≥ 3 不必阻塞;
  • **采用旁路(转发)**时,ALU 的结果可以直接送给下一条指令的 EX 段,不必阻塞;只有 load-use(lw 后紧跟使用其结果的指令)仍需阻塞 1 个周期。
思考题

不考虑转移指令引起的控制冒险,分析循环体每轮执行时的数据相关:哪些指令对之间存在 RAW 相关?不采用旁路和采用旁路时,每轮各需要阻塞多少个周期?

查看解答
写寄存器的指令读该寄存器的指令寄存器距离无旁路有旁路
slt $t1, …beq $t1, …$t1120
sll $t2, …add $t2, $t2, $s0$t2120
add $t2, …lw $t3, 0($t2)$t2120
lw $t3, …add $s2, $s2, $t3$t3121(load-use)
addi $t0, …下一轮的 slt $t1, $t0, …$t02(中间隔着 j)10

不采用旁路时每轮阻塞 2 + 2 + 2 + 2 + 1 = 9 个周期;采用旁路后只剩 load-use 的 1 个周期。编译器还可以通过调整指令顺序消除它,例如把 addi $t0, $t0, 1 移到 lw 与使用 $t3 的 add 之间。

用第 5 章「指令流水线」中的动画可以观察阻塞和旁路的效果。

即时练习

即时练习5 题
点击选项即可作答
  1. 1
    MIPS 指令 beq 位于地址 0040 0100H,其 16 位 offset 字段为 FFFCH,则转移成功时的目标地址为( )。
  2. 2
    某 x86 程序中,地址 0040 1010H 处的指令为 2 字节的 jmp,机器码为 EB F0,则转移目标地址为( )。
  3. 3
    对本节的求和程序,若 n = 100,则共执行的指令条数为( )。
  4. 4
    在 x86 小端机器上,int 型变量 x = 12345678H 存放在地址 1000H 开始的 4 个字节中,则地址 1000H 和 1003H 处的字节分别为( )。
  5. 5
    在采用旁路技术的五段流水线中,下列指令序列中仍然必须阻塞的是( )。
学完了吗?

标记完成后,侧栏会显示进度。进度仅保存在本浏览器中。