1.2 操作码、操作数与寻址模式:工坊术语箱


1.2 操作码、操作数与寻址模式:工坊术语箱

本节摘要:术语是车间的通行证。本节把全册反复使用的名词一次备齐——操作码、操作数、寄存器、寻址模式、字长、程序计数器、标志位——每个术语都配真实汇编片段,而不是干巴巴的定义。承接 1.1 的全程视野,本节给后续所有章节供词。

把术语箱打开清点

1.1 节走完了指令的一生,沿途出现了不少行话。本节把它们集中清点一遍,每个都给出"它在汇编里长什么样、在机器码里占哪部分"。全册后面的章节默认你已经认得这些词,所以这里的每一分钟投入都会在后面省回来。

操作码(opcode):一条指令里表明"做什么"的位段。加、减、跳转、访存,各有专属编号。它是指令编码的灵魂,第 3.2 节整节讨论它的设计策略。在 add a0, a0, 20 里,"加法"这个意图被 RISC-V 编码成低 7 位 0010011 加功能码 000 的组合——注意有些架构把操作拆成"大类码 + 功能码"两层,x86 甚至还要前缀字节参与表意,这些变体第 8 章展厅里逐个看。

操作数(operand):指令加工的对象,回答"对谁做"。操作数有三类来源:寄存器、立即数、内存。add a0, a0, 20 里有两个寄存器操作数(a0)和一个立即数操作数(20)。一条指令需要几个操作数、写在格式的什么位置,不同架构答案迥异——这正是第 2 章流派的分水岭。

寄存器(register):处理器内部的快速储物格。x86-64 有 rax、rbx、rcx 等通用寄存器(整数相关 16 个),RISC-V 的 RV32I 有 x0 到 x31 共 32 个。寄存器在指令编码里只占几个位(32 个寄存器只要 5 位编号),速度却比内存快上百倍,所以编译器拼命把热数据塞进寄存器。x0(RISC-V)这类"恒零寄存器"是个精彩设计:读它永远得零,用它就能拼出清零、传值、比较等花样。

寻址模式(addressing mode):当操作数在内存里时,指令如何表述它的地址。立即寻址(数就在指令里)、寄存器寻址(数在寄存器里)、基址加偏移(地址 = 寄存器值 + 常量)、间接寻址(寄存器里存的还是地址)……模式越多,寻址越灵活,编码和解码也越复杂。3.3 节整节拆解它们的位级表达。

字长(word size)与寄存器宽度:处理器一口能处理的位数。32 位架构一次算 32 位整数,64 位架构一次算 64 位。字长决定了地址空间的宽度——32 位机器最多直接寻址 4 GB 内存,这个上限曾逼得整个行业搬迁到 64 位。x86-64 的寄存器名字里藏着宽度演变史:rax 是 64 位,eax 是它的低 32 位,ax 是 eax 的低 16 位,al 是 ax 的低 8 位。

程序计数器(PC):存放下一条待取指令地址的寄存器。顺序执行时它自动累加(加多少取决于指令长度——定长架构加固定值,变长架构加当前指令的实际长度),遇到跳转指令时被改写。PC 是控制流的锚,5.3 节的分支预测本质就是在"猜 PC 的下一个值"。

标志位(flags)与状态寄存器:运算的副产品登记处。"结果为零""产生进位""结果为负""溢出"这些事实被记在状态寄存器里,后续的条件跳转指令据此决定走哪条路。x86 每次算术运算都自动更新标志位;RISC-V 干脆不设标志位,比较结果直接写进普通寄存器。两种风格在第 2 章和 5.3 节都会再见到。

术语上机:读一段真实汇编

空口清点容易忘,我们上机。下面是两个架构各自实现"把数组前 8 个元素求和"的典型汇编(参数:数组地址、元素个数),认出每个术语的位置:

; x86-64:rdi=数组地址,rax=累加和 xor eax, eax ; 清零累加器(利用自身异或,比 mov 快且省字节) mov ecx, 8 ; 计数器 = 8(立即数操作数) loop: add eax, [rdi] ; 内存操作数:rdi 指向的 32 位值加进 eax add rdi, 4 ; 指针前移(基址寻址的步进) dec ecx ; 计数器减一,顺带更新标志位 jnz loop ; 零标志 ZF 不成立就跳回 loop(PC 被改写) ret
# RISC-V RV64:a0=数组地址,a1 累加 li t0, 8 # t0 = 8(伪指令,真身是 addi t0, zero, 8) mv a1, zero # 累加器清零(真身是 addi a1, x0, 0) loop: lw t1, 0(a0) # 从内存取 32 位元素(基址+偏移寻址) add a1, a1, t1 # 累加 addi a0, a0, 4 # 指针前移 addi t0, t0, -1 # 计数器减一 bnez t0, loop # t0 非零就跳回(比较直接用寄存器,无标志位) ret

两段程序逐行对照,术语就活了:[rdi]0(a0) 是基址寻址的两种书写;jnz loopbnez t0, loop 都是改写 PC 的条件跳转,但前者依赖标志位、后者直接比较寄存器;xor eax, eaxmv a1, zero 异曲同工,后者靠的正是恒零寄存器。你可能还注意到 x86 指令看起来更"密"——一行能同时做取数和累加,RISC-V 每行只干一件事。这个观感差异就是第 2 章 CISC 与 RISC 之争的具象化。

补一门隐藏课:寄存器的分工与伪指令

两段汇编里还藏着两个值得点破的细节。其一是寄存器的角色分工:RISC-V 把 32 个寄存器起了两套名字,一套是 x0 到 x31 的编号,一套是按角色命名的 a0(参数)、t0(临时)、s0(保存)、sp(栈指针)、ra(返回地址)。编译器之间之所以能互相调用函数,靠的正是这套约定——哪些寄存器调用者负责保存、哪些被调者负责保存,写进了调用约定。你会在 5.3 节看到违反它导致的真实故障。x86-64 同样有约定(rdi、rsi 传参,rbx、rbp 由被调者保存),只是历史原因让它看起来不像 RISC-V 那样成体系。

其二是伪指令(pseudo-instruction)。RISC-V 汇编里的 limvbnezret 其实都不存在于硬件编码表里,汇编器会把它们翻译成真指令:li t0, 8 翻译成 addi t0, x0, 8ret 翻译成 jalr x0, ra, 0。伪指令是给人类阅读的糖衣,真正进编码车间的永远是真指令。这个"两层皮"的现象在后面会反复出现——你在反汇编器里看到的、和你在汇编源码里写的,可能不是同一批位串,第 3 章实战时务必以反汇编输出为准。

一张速查表

术语 回答的问题 在编码里通常表现为 相关章节
操作码 做什么 固定位段(可带功能码、前缀) 3.2
操作数 对谁做 寄存器号、立即数字段 2.2、4.1
寻址模式 怎么找到操作数 模式位 + 偏移/基址字段 3.3
字长 一口处理多少位 寄存器宽度、立即数宽度 4.1
程序计数器 下一条指令在哪 不可见的专用寄存器 5.3
标志位 上次运算结果如何 状态寄存器的各标志 5.3

这张表建议打印或抄在笔记本首页。读到后面任何一节迷路时,先问自己"现在讨论的是表里的哪一行",位置感立刻回来。比如读到 3.2 节你会发现整节都在讲第一行;读到 5.3 节会发现分支预测是第五行和第六行的合奏。

容易踩的坑

新手最常见的混乱是把"寄存器名"当成 ISA 的全部。寄存器确实在契约里(编译器必须知道有哪些格、宽度多少),但"内部另有一倍数量的物理寄存器供乱序执行重命名"属于实现细节——架构只规定你看得见的那个名字。7.1 节讲系统编程时会再次遇到"可见与不可见"的分界。

第二个坑:认为"寻址模式越多越好"。模式确实省指令,但每种模式都要在编码里占位、在解码时占电路。x86 有十余种寻址模式,代价是 3.3 节里你会看到的复杂 ModRM 机制;RISC-V 只保留基址加偏移一种内存寻址,其余交给指令组合。灵活与简单的账怎么算,等你看完第 2 章自有判断。

本节要点回顾

  • 操作码管"做什么",操作数管"对谁做",寻址模式管"怎么找到"——三件套构成任何指令的语义骨架。
  • 寄存器是编码里的"短线程储物格",位数少、速度快;恒零寄存器让许多伪指令无需专用编码。
  • 字长决定一次运算的宽度和地址空间上限;x86-64 寄存器名的分层(rax/eax/ax/al)记录着宽度演化。
  • PC 是控制流的锚:顺序执行自动累加,跳转指令直接改写。
  • 标志位是 x86 风格的条件基础,RISC-V 用普通寄存器比较替代——两种风格贯穿后面所有控制流讨论。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U