--- title: "04-指令系统" created: 2026-08-29 tags: - 基础与理论 - 计算机组成原理 - "408" --- # 04 指令系统 > 📚 本文是 [[00-计算机组成原理总览|计算机组成原理]] 的第 4 篇,相关系列见 [[00-基础与理论|基础与理论]]。 存储程序思想([[01-系统概述]])说"程序以二进制存在存储器里"——那个二进制就是**指令**。指令系统(指令集架构 ISA)是**软件和硬件的分界线**:往上是编译器看得见的抽象,往下是 CPU 实打实的电路。本章三件事:**指令长什么样**(格式与扩展操作码)、**操作数从哪来**(十种寻址方式)、**设计取向**(CISC vs RISC)。 ## 一、指令格式 一条指令 = **操作码(干什么)+ 地址码(对谁干)**。 按地址码个数分类: | 形式 | 例 | 特点 | |---|---|---| | 零地址 | NOP、栈顶运算(`push/pop` 架构) | 无需操作数地址,或隐含在栈顶 | | 一地址 | `ACC ← ACC OP M`(累加器隐含) | 只给一个操作数 | | 二地址 | `ADD R1, R2` → R1 ← R1 + R2 | 最常见 | | 三地址 | `ADD R1, R2, R3` | 结果单独存放(RISC 风格) | ⚠️ 定长指令字 vs 变长指令字:RISC 用定长(好流水线),x86 用变长(1~15 字节,省空间但解码噩梦——[[05-CPU]] 里"x86 解码复杂"的伏笔在这)。 ## 二、扩展操作码:不定长操作码的设计 操作码不必等长:**短指令给常用操作,长指令留足地址空间**——哈夫曼编码思想在指令集里的应用。 **手算例题(408 必考)**:指令字长 16 位,要求三地址指令 15 条、二地址指令 15 条、一地址指令 15 条,每个地址码 4 位,问还能留几条零地址指令? 1. 三地址:操作码 4 位,16 种组合用 15 条 → 剩下 `1111` 做**前缀** 2. 二地址:用 `1111` 开头 + 4 位扩展 = 8 位操作码 → 16 种,用 15 条 → 又剩 `1111 1111` 3. 一地址:`1111 1111` + 4 位 = 12 位操作码 → 16 种,用 15 条 → 剩 `1111 1111 1111` 4. 零地址:12 个 1 做前缀 + 4 位 = **16 条** ✅ 规律口诀:**每往下一个地址级,可用组合 = 16 − 已用 15 = 1 条前缀继续扩**。考试常反过来问"还剩多少条",就是顺着这条链乘下来。 ## 三、寻址方式:操作数到底在哪 指令里写的不是数据本身,是"**数据的地址的地址的……**"——寻址方式决定这一串 dereference 要做几次。 ### 十种寻址方式全表 (EA = 有效地址,最终算出来的操作数真实地址;A = 指令中给的地址码) | 寻址方式 | 有效地址 EA | 访存次数(取数) | 特点/用途 | |---|---|---|---| | 立即寻址 | 操作数=A 本身 | **0** | 最快,但位数受限;装常数 | | 直接寻址 | EA = A | 1 | 简单;寻址范围受 A 位数限制 | | 间接寻址 | EA = M[A] | **2**(先取 A 的内容再取数) | 扩大范围;慢 | | 寄存器寻址 | 操作数在寄存器 Ri | **0** | 快、指令短 | | 寄存器间接 | EA = (Ri) | 1 | 常用于指针 | | 相对寻址 | EA = (PC) + A | 1 | **程序浮动/转移**(A 是偏移量,补码可正可负) | | 基址寻址 | EA = (基址寄存器 BR) + A | 1 | **操作系统用**:重定位/多道程序,BR 由 OS 管,用户改不了 | | 变址寻址 | EA = (变址寄存器 IX) + A | 1 | **用户用**:数组循环遍历,A 是数组首址、IX 是下标 | | 堆栈寻址 | EA = (SP)(隐含) | 1 | push/pop,函数调用 | | 页面寻址 | EA 高位 = PC 高位,低位 = A | 1 | 分页思想的简化版 | ### 基址 vs 变址——最容易混的一对 | 维度 | 基址寻址 | 变址寻址 | |---|---|---| | 谁的内容是"基准" | **基址寄存器**(系统给的大基准) | **指令里的 A**(程序内的小基准) | | 谁变 | A 变(用户改位移) | IX 变(用户改下标) | | 面向 | **系统**:程序重定位、进程隔离 | **用户**:数组、字符串循环 | 💡 一句话记忆:**基址是"搬家不改代码"(OS 管),变址是"循环遍历数组"(用户管)**。x86 的 `mov eax, [ebx+esi*4]` 就是基址+变址+比例因子的组合。 ⚠️ 访存次数是 408 高频陷阱:**立即 0 次、寄存器 0 次、直接/相对/基址/变址 1 次、间接 2 次**。若问"执行一条指令共访存几次",还要加上取指令本身的 1 次(变长指令集里可能更多)。 ### 相对寻址的转移距离 A 是**补码偏移**,所以一条 `jmp -4` 能向上跳,`jmp +100` 向下跳。取指后 PC 已指向**下一条**指令,所以 EA = (PC)已更新 + A——408 计算题最容易踩的点:**用更新后的 PC,别用取指前的**。 ## 四、CISC vs RISC | 维度 | CISC(复杂指令集) | RISC(精简指令集) | |---|---|---| | 代表 | x86 / x86-64 | ARM、RISC-V、MIPS | | 指令 | 多(数百条)、变长 | 少(<200 条)、**定长** | | 寻址方式 | 丰富(十几种) | 精简(几种) | | 访存 | 多种指令都可访存 | **只有 Load/Store 指令访存** | | 执行 | 各指令周期差异大 | 绝大多数**单周期**,利于流水线 | | 控制 | **微程序控制**为主 | **组合逻辑(硬布线)**为主 | | 寄存器 | 少(x86 经典 8 个通用) | 多(ARM/RISC-V 32 个) | | 优化方向 | 用指令丰富度换程序短 | 用编译器 + 流水线换速度 | 💡 现代融合:x86 外部是 CISC 指令集,**内部把它翻译成类 RISC 微操作(μop)**再进流水线——表面 CISC、内心 RISC。ARM 赢在移动端功耗,x86 赢在兼容性生态。 ## 五、x86 与 RISC-V 一瞥 - **x86-64**:变长指令、两种端序传统(实际小端)、8→16 个通用寄存器;`mov rax, [rbx]` 一条指令横跨内存访问 - **RISC-V**:开源 ISA,定长 32 位,只有 `add/sub` 等基础指令 + `load/store` 访存;指令少到"不设除法溢出标志"也能活 - 读汇编的通用套路:看**操作码 → 找寻址方式 → 心里算 EA**,三步走通任何架构 ## 六、盲点自测 1. 一地址指令的操作数另一个在哪?(隐含在累加器 ACC) 2. 扩展操作码:16 位指令、4 位地址码,三地址 15 条后还能扩几条二地址?(15 条,前缀 1111) 3. 哪些寻址方式取数不用访存?(立即、寄存器) 4. 基址和变址谁是系统的、谁面向数组循环?(基址系统/重定位,变址用户/数组) 5. 相对寻址的 EA 用取指前还是取指后的 PC?(**取指后已自增的 PC**) 6. RISC"只有哪两类指令能访存"?(Load / Store) ## 七、动手玩 ```bash # x86-64 反汇编看看"变长指令"长什么样(macOS 自带) echo -n 'int main(){return 1+2;}' > /tmp/t.c && clang /tmp/t.c -o /tmp/t objdump -d /tmp/t | head -30 # 观察每条指令的字节数都不一样 ``` 有 Linux 机器的话,对比 `gcc -S` 同一段代码的 x86 输出和 RISC-V 输出(`riscv64-unknown-elf-gcc` 或 Compiler Explorer 网页版),直观感受定长 vs 变长。 ## 参考资料 - 王道《计算机组成原理考研复习指导》第 4 章 - CSAPP 第 3 章(x86-64 汇编,把寻址方式用到真实代码里) - 《计算机组成与设计:硬件/软件接口》Patterson & Hennessy——RISC 思想的正源 ⬅️ [[03-存储系统|存储系统]] 🏠 [[00-基础与理论|00-基础与理论]] ➡️ [[05-CPU|CPU]]