指令系统

考点定位

  • 大纲章节:指令格式(定长/扩展操作码)、指令的寻址方式、数据的对齐与大端小端存放、CISC 与 RISC 的基本概念、高级语言程序与机器级代码之间的对应(x86/MIPS 精神)
  • 常见题型:扩展操作码设计(高频计算)、寻址方式 EA 计算与访存次数(选择高频)、大小端判断(选择)、汇编与栈帧(选择/小计算)
  • 考查频率:高(每年 4-8 分,扩展操作码与寻址方式几乎轮流出题;新大纲加入的"高级语言与机器码"近年在选择题反复出现)
  • 前置知识:补码与定点数(相对寻址位移量、栈帧地址计算)见数据的表示和运算;存储器编址与 MAR/MDR 见存储器层次结构
  • 工程师读者注意:你写过汇编、读过反汇编,但考试考教材口径的访存次数计数与扩展操作码的编码空间计算——计数规则与工程直觉略有出入,按本节口径作答

一、指令格式

1.1 指令的两要素

指令=操作码 OP+地址码 A\text{指令} = \text{操作码 OP} + \text{地址码 A}

  • 操作码:指明做什么(加、转移、访存……),其位数决定指令条数上限(nn 位 OP 最多 2n2^n 条)。
  • 地址码:指明操作数/结果/下一条指令的来源,可以是主存地址、寄存器编号或立即数。
  • 指令字长 = 操作码位数 + 各地址码位数,可以等于、大于或小于机器字长(见计算机系统概述三字长对比)。

1.2 按地址码个数分类(访存次数是考点)

以下计数口径(唐教材口径):取指令本身 1 次访存(PC 顺序提供下条指令地址,不含在"执行"计数内另算),执行阶段另行统计取操作数与存结果。选择题常问"执行一条 XX 地址指令共访存几次",按"取指 + 取操作数 + 存结果"合计。

类型格式语义执行阶段访存(除取指外)
四地址OP A1 A2 A3 A4(A1) OP (A2) → A3,下条指令地址 = A4取 2 数 + 存结果 = 3 次
三地址OP A1 A2 A3(A1) OP (A2) → A3,下条由 PC 给出3 次
二地址OP A1 A2(A1) OP (A2) → A1(或 → ACC)3 次(结果覆盖目的数时:若目的为寄存器则少 1 次存数)
一地址OP A1(ACC) OP (A1) → ACC(隐含 ACC)1 次
零地址OP操作数隐含在堆栈顶(硬堆栈/寄存器对)0 次(软堆栈则有出/入栈访存)

规律:地址码越少,指令越短、通用寄存器依赖越高;隐含约定(ACC、堆栈、PC)替代了显式地址。二地址 R 型(寄存器-寄存器)执行阶段 0 次访存——RISC 的基础。

1.3 定长操作码 vs 扩展操作码

  • 定长操作码:OP 固定 nn 位,最多 2n2^n 条指令,译码快,但浪费编码空间(短指令的地址码空间被 OP 占死)。
  • 扩展操作码(变长操作码):短指令 OP 短、留出部分编码作"扩展标志"引出长 OP。高频计算考点,见 1.4。

1.4 扩展操作码设计(高频计算,完整步骤 + 验证)

设计四步(背下来,考试直接套):

  1. 定框架:指令字长、每个地址字段位数 → 各级 OP 可用位数 = 字长 − 地址字段总位数。
  2. 从最短 OP 开始分配:本级留出全 1 编码(或若干高位前缀)作扩展标志,其余编码分配给本级指令。
  3. 逐级扩展:下一级 OP = 上一级扩展标志 + 新增位,同样再留新标志。
  4. 验证编码空间:∑各级 本级条数×2剩余未用位数=2指令字长\sum_{\text{各级}}\ \text{本级条数} \times 2^{\text{剩余未用位数}} = 2^{\text{指令字长}}。这一步能查出多算/漏算。

标准例题(模拟题):指令字长 16 位,每个地址字段 4 位,要求:三地址 15 条、二地址 15 条、一地址 15 条,零地址最多多少条?

第一级(三地址,OP 4 位):编码 0000~1110 共 15 条,留 1111 作扩展标志。

第二级(二地址,OP 8 位,形如 1111 xxxx):xxxx 取 0000~1110 共 15 条,留 1111 1111。

第三级(一地址,OP 12 位,形如 1111 1111 xxxx):15 条,留 1111 1111 1111。

第四级(零地址,OP 16 位,形如 1111 1111 1111 yyyy):yyyy 任意 4 位 → 24=162^4 = 16 条。

验证:15×212+15×28+15×24+16×1=61440+3840+240+16=65536=21615\times2^{12} + 15\times2^{8} + 15\times2^{4} + 16\times1 = 61440 + 3840 + 240 + 16 = 65536 = 2^{16} ✓。

反向题套路:"已知字长 16 位、地址 4 位,三地址 12 条、二地址 60 条、一地址 60 条,零地址最多多少条?"

  • 三地址用 12 个前缀(0000~1011),剩 4 个前缀可扩展。
  • 二地址编码空间 = 4×24=644\times2^4 = 64,用 60 条,剩 4 个编码可扩展。
  • 一地址空间 = 4×24=644\times2^4 = 64,用 60 条,剩 4 个编码。
  • 零地址最多 = 4×24=644\times2^4 = 64 条。
  • 验证:12×212+60×28+60×24+64=49152+15360+960+64=6553612\times2^{12} + 60\times2^8 + 60\times2^4 + 64 = 49152+15360+960+64 = 65536 ✓。

关键心法:每放弃一个编码,换来的是 2新增位数2^{\text{新增位数}} 个下一级编码;"留 1 换 16"这笔账要会算。

二、指令的寻址方式

2.1 指令寻址(找下一条指令)

  • 顺序寻址:(PC) + "1"(一条指令字长)→ PC。注意这个"1"是一条指令的长度,不是字节 1;定长指令且按字编址时 +1,变长指令按实际字节数加。
  • 跳跃寻址:执行转移类指令时,下条地址来自指令的地址字段(直接/间接/相对寻得),PC 被改写。

2.2 数据寻址(找操作数)——九种方式与 EA 计算式

约定:A 为指令中的地址码字段,(X) 表示 X 单元的内容,EA 为操作数的有效地址。

寻址方式EA 计算式取操作数访存次数特点/用途
立即寻址操作数 = A 本身(无 EA)0取指时已随指令取入,速度最快;A 位数限制数值范围;只为源操作数
直接寻址EA = A1简单;寻址范围受 A 位数限制
隐含寻址操作数在 ACC(或约定寄存器)0缩短指令字长
间接寻址EA = (A)2(一次间址)扩大寻址范围、便于子程序返回地址处理;N 重间址 N+1 次
寄存器寻址操作数 = (Ri),EA 无主存含义0不访存,快;指令短
寄存器间接寻址EA = (Ri)1寄存器放地址,寻址范围大且只访存一次
相对寻址EA = (PC) + A1A 为补码位移量可正可负;程序浮动(位置无关代码)、转移指令
基址寻址EA = (BR) + A1BR 面向操作系统,程序执行期间基准不变、A 可变;用于程序重定位、扩大寻址范围
变址寻址EA = (IX) + A1IX 面向用户,程序中 A 不变、IX 可变;数组遍历、循环处理
堆栈寻址EA = (SP)(软堆栈)1(入栈/出栈;硬堆栈 0)PUSH 先/后修改 SP 的口径见下

计数口径提醒:表中是取一个操作数的访存次数;一条指令总访存 = 取指 1 次 + 各操作数取数 + 存结果(若有)。

相对寻址计算例:转移指令本身地址为 2000(字地址),指令字长 1 个字,位移量 A = −3(补码),则目标 EA = (PC) + A = (2000 + 1) + (−3) = 1998。最大坑:(PC) 是"本条指令地址 + 指令字长"(即下一条指令地址),不是本条指令地址。

基址 vs 变址的快速判别(选择题高频):"由操作系统管理、程序执行中内容不变" → 基址;"面向用户、执行中由用户/程序修改" → 变址。数组处理 a[i]:把数组首地址放 A、i 放 IX,一条指令在循环中不变——这是变址最典型应用。

堆栈 SP 修改口径:408 常用"入栈先减后存(先 (SP)−1 → SP,再存入),出栈先取后加"(栈向低地址生长,x86 口径)。若题目采用其他口径(自底向上生长)会明确说明,作答以题目图为准。

2.3 综合取数手推(大题模板)

设主存与寄存器状态:(R1)=0x200,(PC)=0x100(已指向下条),(0x200)=0x800,(0x800)=0x15,(BR)=0x600,(IX)=5,指令中 A = 0x200。各寻址方式的 EA 与取值:

方式EA取到的操作数
立即—0x200(A 本身就是数)
直接0x200(0x200) = 0x800
间接(0x200) = 0x800(0x800) = 0x15
寄存器(R1)寄存器(R1) = 0x200
寄存器间接(R1)(R1) = 0x2000x800
相对(PC)+A = 0x100+0x200 = 0x300(0x300)
基址(BR)+A = 0x600+0x200 = 0x8000x15
变址(IX)+A = 5+0x200 = 0x205(0x205)

(按寻址方式逐级展开取数为动态内容,待动画化。)

三、数据的对齐与大端、小端存放

3.1 边界对齐

设存储字长 32 位、按字节编址、一次访存取一个存储字:

  • 边界对齐:大小为 2k2^k 字节的数据,其地址必须是 2k2^k 的倍数(字地址低 2 位为 00、半字低 1 位为 0)。任何数据一次访存取出。
  • 不对齐存放:一个字可能跨两个存储字 → 需要 2 次访存并拼接,硬件代价大。

408 口径:按边界对齐是空间换时间(可能浪费尾部字节)。选择题问"某半字数据存放在地址 0x1002,是否对齐"——半字要求偶地址,0x1002 对齐 ✓;若在 0x1003 则不对齐。

3.2 大端与小端

多字节数据的字节序(设 32 位数 0x12345678 存于首地址 0x400,按字节编址):

地址大端(big-endian)小端(little-endian)
0x40012(最高字节)78(最低字节)
0x4013456
0x4025634
0x4037812
  • 大端:高位字节在低地址,符合书写/阅读顺序;网络字节序、部分 RISC。
  • 小端:低位字节在低地址;x86、多数 ARM 默认配置。
  • 判别代码(高频选择题素材):
union { unsigned int i; unsigned char c; } u;
u.i = 1;
printf("%d\n", u.c);  // 输出 1 → 小端;输出 0 → 大端

四、CISC 与 RISC(对比表必背)

对比项CISC(复杂指令集计算机)RISC(精简指令集计算机)
指令数量多(数百条以上)少(<100 条典型)
指令长度变长定长
寻址方式多少
访存指令多数指令可访存只有 Load/Store 访存,其余寄存器-寄存器
通用寄存器少多(几十个)
执行时间各指令差异大多数单周期,适合流水线
控制方式微程序控制为主组合逻辑(硬布线)为主
指令使用频率常用指令占比低(80/20 规律的反面例证)高频精选
编译编译程序简单依赖优化编译
代表x86MIPS、ARM、RISC-V、LoongArch

必背结论:RISC 不一定兼容 CISC;RISC 的"取指、译码、执行"规整性是流水线(后续章节)的基础;CISC 对特殊任务(如字符串处理)单条指令能力强。选择题常拿"RISC 完全不采用微程序控制""RISC 指令一定比 CISC 少就一定快"这类绝对化表述设错。

五、高级语言与机器级代码(新大纲,x86/MIPS 精神)

5.1 从 C 语句到机器指令

C 语句 → 编译器 → 汇编 → 汇编器 → 机器码。工程师对这条链路熟,考试只考两类:读小段汇编反推 C 语义、栈帧变化。x86(32 位,Intel 语法)对照示例:

int add(int a, int b) { return a + b; }
add:  push ebp          ; 保存调用者的栈帧基址
      mov  ebp, esp     ; 建立本函数栈帧
      mov  eax, [ebp+8] ; 取参数 a(调用者压栈,位于返回地址之上)
      add  eax, [ebp+12]; 加参数 b
      pop  ebp          ; 恢复调用者栈帧
      ret               ; 弹出返回地址送 PC

5.2 AT&T 与 Intel 汇编格式差异(选择考点)

差异点Intel 语法AT&T 语法(Linux 默认)
操作数顺序目的在前 mov ebx, eax源在前 mov %eax, %ebx(同为 eax→ebx)
寄存器eax前缀 %:%eax
立即数5前缀 $:$5
操作数大小mov byte ptr后缀 b/w/l/q:movb、movl
内存寻址[base + index*scale + disp]disp(base, index, scale),如 movl 8(%ebp), %eax = mov eax, [ebp+8]
跳转目标jmp labeljmp label(函数/远目标加 $)

判别技巧:看到 %、$、(%ebp) 就是 AT&T;看到方括号就是 Intel。

5.3 常见指令对栈帧的影响(必背行为表)

设 32 位 x86,栈向低地址生长,push = SP 先减 4 再存,pop = 先取再加 4:

指令对栈的影响访存对 PC
push srcSP−4,src 存入栈顶1 次写—
pop dst取栈顶送 dst,SP+41 次读—
call f压返回地址(push 下条指令地址)+ 跳转1 次写跳到 f
ret弹返回地址送 PC1 次读跳回
leavemov esp, ebp; pop ebp(拆栈帧)1 次读—
enter/序言 push ebp; mov ebp,esp; sub esp,N建栈帧、开 N 字节局部区——

要点:①call/ret 各隐含一次栈访存且改 PC,jmp 只改 PC 不访存;②参数与局部变量的访问通过 ebp 相对寻址([ebp+8] 参数、[ebp−4] 局部变量),正是 2.2 节基址/相对寻址的工程实例;③栈帧推演题画图跟踪 SP/EBP 两个箭头即可。

(call/ret 栈帧建立与销毁为动态内容,待动画化:SP/EBP 双指针随指令移动。)

5.4 MIPS 精神(简要)

MIPS 是 408 教材 RISC 范例:32 位定长指令、32 个通用寄存器($zero 恒 0、$sp 栈指针、$ra 返回地址)、只有 lw/sw 访存、jal 跳转并链接(返回地址存 $ra)、jr $ra 返回、无 push/pop 硬件指令(栈操作用 addi $sp,$sp,-4 + sw 手工完成)。与 x86 对照着记:x86 栈指令硬件化,MIPS 全软件管理——这一对比本身就能出选择题。

例题

例题 1(模拟题) 某机指令字长 16 位,每个地址字段 4 位。若采用扩展操作码:三地址指令 15 条、二地址指令 15 条、一地址指令 15 条,则零地址指令最多( )条。

解:由 1.4 节标准推导,零地址 = 24=162^4 = 16 条(验证 15×212+15×28+15×24+16=6553615\times2^{12}+15\times2^8+15\times2^4+16 = 65536)。答 16。

例题 2(模拟题) 变址寻址方式中,操作数 EA =( )。 A. A B. (A) C. (IX) + A D. (PC) + A

解:C。变址 EA = (IX) + A;D 是相对寻址;B 是间接寻址;A 是直接寻址。错因提醒:基址与变址形式相同(基址是 (BR)+A),题目靠"寄存器名"区分,看清 IX 还是 BR。

例题 3(模拟题) 转移指令占 2 个字节、存放在主存字节地址 1000 处(按字节编址),相对寻址位移量为 12(补码),求转移目标地址。

解:(PC) = 1000 + 2 = 1002(下一条指令地址),EA = 1002 + 12 = 1014。错因提醒:用 1000 而非 1002 作基数是最常见错误——PC 在取指后已加上本条指令长度。

例题 4(模拟题) 一次间接寻址的取数指令,从取指令到取得操作数共访存( )次。

解:取指 1 次 + 按 A 取 EA 1 次 + 按 EA 取操作数 1 次 = 3 次。若问"执行阶段"则为 2 次。审清"共"还是"执行阶段"。

例题 5(模拟题) x86 机器执行 call f 后栈顶内容是( )。 A. f 的首条指令地址 B. 返回地址(call 的下一条指令地址) C. EBP 旧值 D. 参数

解:B。call 压入的是返回地址;f 自己的序言才压 EBP。错因提醒:目标地址经 PC 跳转、不进栈。

例题 6(模拟题) 判断:AT&T 语法 movl 8(%ebp), %eax 与 Intel 语法 mov eax, [ebp+8] 语义相同。

解:正确。二者都把 [ebp+8] 处的 32 位数送 eax。要点:AT&T 源在前,8(%ebp) 即 Intel 的 [ebp+8]。

常见坑

  1. 相对寻址的 (PC) 是下一条指令地址(本条地址 + 指令字长),位移量用补码可正可负。
  2. 基址寄存器面向系统(BR 执行中不变,A 变),变址寄存器面向用户(IX 变,A 不变)——两者 EA 形式一样,别混。
  3. 立即寻址没有 EA;问"EA = ?"的选项里立即寻址常被写成 EA = A(错误口径,A 是操作数不是地址)。
  4. 一次间址取操作数 2 次访存,N 重间址 N+1 次;别忘取指本身 1 次是否计入要审题。
  5. 扩展操作码验证公式 ∑条数×2剩余位数=2字长\sum \text{条数}\times2^{\text{剩余位数}} = 2^{\text{字长}} 必做,多算一级或漏留扩展标志当场暴露。
  6. 扩展操作码各级指令数上限:上级每留 1 个编码,下级多 2新增位数2^{\text{新增位数}} 条;"每级都取满 15"只是习惯,不是规定。
  7. 小端是低字节在低地址;union 判别输出 1 → 小端。
  8. 不对齐的 32 位数据最多需 2 次访存(跨两个存储字),不是 4 次。
  9. RISC 也有访存指令(Load/Store),"RISC 完全不能访存"是错误表述;不能访存的是运算类指令。
  10. call 访存且改 PC,jmp 只改 PC 不访存——问"哪条指令改变了栈"时 jmp 不算。
  11. MIPS 无 push/pop,返回地址存 $ra 寄存器(不自动压栈,需软件保存)。

自测

  1. 指令字长 32 位,每个地址字段 6 位。不扩展时三地址指令最多多少条?若三地址指令留出 8 个编码作扩展标志,二地址指令最多多少条?
  2. 寄存器间接寻址取一个操作数访存几次?与间接寻址(一次间址)差在哪?
  3. 某机器按字节编址,call 指令长 4 字节、位于 0x0800,CPU 执行它压栈的返回地址是多少?
  4. 32 位存储字、按字节编址,地址 0x1001 处存放一个 16 位半字,是否边界对齐?
  5. 写出 AT&T 语法中"把立即数 10 送 eax"与"取 [eax+4*ebx+8] 处 32 位数送 ecx"的指令。

自测答案与简析

  1. 三地址 OP = 32 − 18 = 14 位,最多 214=163842^{14} = 16384 条。留 8 个编码作扩展标志后,二地址 OP = 20 位(14 位前缀 + 6 位新增),最多 8×26=5128\times2^6 = 512 条。心法:上级每放弃 1 个编码,下级换得 262^{6} 个。
  2. 1 次(按寄存器内容访主存)。间接寻址要 2 次:先访主存取 EA,再访主存取数;寄存器间接把"取 EA"放进寄存器,省一次主存访问。
  3. 0x0804(下一条指令地址 = 0x0800 + 4)。
  4. 不对齐:16 位半字要求偶地址,0x1001 是奇地址,取出需 2 次访存。
  5. movl $10, %eax;movl 8(%eax,%ebx,4), %ecx。要点:AT&T 立即数 $、寄存器 %、内存 disp(base,index,scale)。