本地资料指令操作码
SCHEDULE LOCAL30 个小节
暂无关联题目选中文字可高亮或加下划线
选中文字高亮 · 下划线

指令操作码

复习提示(高优先级):把操作码的语义与数据通路、条件标志、PC 更新和栈操作对应起来理解。

本节要和 CPU 中的 控制器 放在一起理解,也是后续 CPU 的基础。以协处理器、字符串操作等 x86 专有扩展为例时,重点不是死记每条助记符,而是把它们对应到数据通路、寄存器、条件标志和控制流。

不同计算机架构的指令操作码不相同,但是其中涉及到的功能却大同小异。本节以 x86 平台的指令为例,说明一下指令操作码的主要分类和功能。

数据传输指令

数据传送

数据传送指令在 x86 中就是 MOV,将第二个操作数(寄存器的内容、内存中的内容或常数值)复制到第一个操作数(寄存器内存),可以实现 寄存器内存之间的 数据传送

  • 语法
  • 实例
; 数据传送指令语法
mov <reg>, <reg>   ; 复制寄存器值
mov <reg>, <mem>   ; 从内存加载数据到寄存器
mov <mem>, <reg>   ; 把寄存器值存入内存
mov <reg>, <con>   ; 立即数赋值给寄存器
mov <mem>, <con>   ; 立即数赋值给内存
; 数据传送指令实例
mov eax, ebx       ; 把 ebx 复制到 eax
mov eax, [var]     ; 把变量 var 的值存入 eax
mov [var], eax     ; 把 eax 的值存入变量 var
mov ecx, 100       ; 将 100 赋值给 ecx
mov byte ptr [var], 5  ; 只修改 var 指向的 1 字节
MOV 在寄存器、内存和立即数之间传送数据的允许方向

栈操作

堆栈指的是程序的运行栈。本文沿用 x86 常见的“从高地址向低地址增长”图示;栈增长方向和栈指针更新规则属于 ISA/ABI 约定,并非所有体系结构都相同。PUSH 指令将 数据压入栈顶,POP 指令从栈顶取出 数据,并存入 寄存器或者 内存单元

  • 语法
  • 实例
  • 入栈操作解释
  • 出栈操作解释
push <reg>    ; 将寄存器值压入堆栈
push <mem>    ; 将内存值压入堆栈
push <con>    ; 将立即数压入堆栈

pop <reg>     ; 从堆栈弹出值存入寄存器
pop <mem>     ; 从堆栈弹出值存入内存
push eax      ; 将 eax 压入栈
push 10       ; 将 10 压入栈
pop ebx       ; 弹出栈顶的值存入 ebx
; PUSH 指令等同于以下指令序列
sub esp, 4      ; esp 向低地址移动
mov [esp], eax  ; 把 eax 的值写入栈顶
; POP 指令等同于以下指令序列
mov ebx, [esp]  ; 从栈顶读取值
add esp, 4      ; esp 向高地址移动
  • 当执行 PUSH 指令时,需要将 ESP 的值 减去数据大小,然后将 数据写入新地址处。
  • 当执行 POP 指令时,需要将栈顶的 数据(即 [ESP] 处的值)读取到目标 寄存器内存,然后将 ESP 的值 加上数据大小,即弹出 数据

下图给出了一个入栈出栈指令的实例,通过 PUSHPOP 指令实现了 寄存器 EAXEBX 内容交换:

PUSH 与 POP 改变 ESP 并交换 EAX 和 EBX 内容的过程

算术和逻辑运算指令

加减

可以通过 ADDSUB 两个指令实现 加减 操作:

  • ADD 指令执行 加法,将结果存入第一个操作数。

  • SUB 指令执行 减法:第一个操作数减去第二个操作数。

  • 语法

  • 实例

add <reg/mem>, <reg/mem/con>   ; 加法
sub <reg/mem>, <reg/mem/con>   ; 减法
add eax, ebx    ; eax ← eax + ebx
sub eax, 10     ; eax ← eax - 10
add [var], cl   ; var ← var + cl

乘除

在 x86 架构中,乘除法使用专门的指令 MULIMULDIVIDIV 以及乘除运算电路。单操作数形式大多依赖默认 寄存器(如 32 位操作数时的 EAXEDX),操作前需准备好相关 寄存器的值。

  • 语法
  • 实例
; 无符号乘法
mul <reg/mem>   ; EAX × 操作数 → EDX:EAX
; 有符号乘法
imul <reg/mem>              ; EAX × 操作数 → EDX:EAX
imul reg, <reg/mem>         ; reg ← 被乘数 × 操作数
imul reg, <reg/mem>, <imm>  ; reg ← 操作数 × 常数
; 无符号除法
div <reg/mem>   ; 被除数:EDX:EAX,商 → EAX,余数 → EDX
; 有符号除法
idiv <reg/mem>  ; 被除数:EDX:EAX,商 → EAX,余数 → EDX
; 无符号乘法
mov eax, 6
mov ebx, 4
mul ebx        ; → EAX = 24, EDX = 0

; 有符号乘法
mov eax, -6
mov ebx, 4
imul ebx       ; → EAX = -24, EDX = 0

imul ecx, ebx      ; ecx ← ecx * ebx
imul edx, ebx, 10  ; edx ← ebx * 10

; 无符号除法
mov edx, 0     ; 高位清零
mov eax, 20
mov ecx, 3
div ecx        ; → EAX = 6, EDX = 2 (20 ÷ 3)

; 有符号除法
mov eax, -20
cdq            ; EDX ← EAX 的符号扩展
mov ecx, 3
idiv ecx       ; → EAX = -6, EDX = -2

除法指令可能会触发异常

  • 除数等于 0:无符号或有符号除法中,如果除数是 0,会导致数学上未定义,立即触发异常。
  • 结果溢出:特别常见于 IDIV 有符号除法中,被除数是最小负数 0x80000000,除以 -1 会得到 0x80000000(超出 32 位有符号整数范围)。

加减乘除通过什么硬件完成

运算类型 使用的硬件单元 典型相对延迟
加 / 减 ALU(加法器电路) 较低
乘法 乘法器(Multiplier) 通常高于加减
除法 除法器(Divider) 通常高于乘法

实际延迟由位宽和微体系结构决定,不能把表中的相对关系当成固定周期数。

位操作

位操作用于对 寄存器内存中的二进制位直接进行按位运算,位操作包含如下类型:

  • AND:保留指定位,其它位清零;
  • OR:将指定位设置为 1;
  • XOR:将指定位翻转(0 ↔ 1);
  • NOT:将所有位取反(补码的按位非);
AND OR XOR NOT 对两个四位操作数逐位运算的结果
  • 语法
  • 实例
and <reg/mem>, <reg/mem/con>   ; 按位与:目标 ← 目标 & 源
or  <reg/mem>, <reg/mem/con>   ; 按位或:目标 ← 目标 | 源
xor <reg/mem>, <reg/mem/con>   ; 按位异或:目标 ← 目标 ^ 源
not <reg/mem>                  ; 按位取反:目标 ← ~目标
; 按位与:清除低位
and eax, 0xF0      ; eax ← eax & 0xF0,仅保留高 4 位

; 按位或:设置低位
or eax, 0x0F       ; eax ← eax | 0x0F,将低 4 位全部置为 1

; 按位异或:清零技巧
xor eax, eax       ; eax ← eax ^ eax,结果为 0

; 按位取反:翻转全部位
not eax            ; eax ← ~eax

自增自减

自增(INC)与自减(DEC)分别等价于对操作数 加 1减 1,常用于循环计数或栈指针调整等场景。

  • INC 的数值结果相当于加 1。
  • DEC 的数值结果相当于减 1。

x86 细节INC/DEC 不更新 CF,而 ADD/SUB 会更新 CF。因此它们不能在依赖进位标志的代码中无条件互换;其他 ISA 的标志位规则也需按题设判断。

  • 语法
  • 实例
inc <reg/mem>    ; 加 1:目标 ← 目标 + 1
dec <reg/mem>    ; 减 1:目标 ← 目标 - 1
inc eax          ; eax ← eax + 1
dec ebx          ; ebx ← ebx - 1
inc byte [cnt]   ; 将内存中 cnt 所指的字节加 1

比较

CMP 指令用于 比较 两个操作数的差值,不保存结果,只更新条件标志位(如 ZFSFCFOF),常与条件跳转指令配合使用。

本质上,CMP A, B 等价于 SUB A, B,但不会改变 A 的值。

  • 语法
  • 实例
cmp <reg/mem>, <reg/mem/con>  ; 比较:目标 - 源,仅影响标志位
cmp eax, ebx       ; 比较 eax 和 ebx
je  equal_label    ; 若 eax == ebx,跳转
cmp byte [x], 0
jl  less_than_zero ; 若 [x] 为负数,跳转

比较运算和加减操作一样,会影响以下条件标志

  • ZF(Zero Flag):结果是否为零
  • SF(Sign Flag):结果是否为负
  • CF(Carry Flag):是否产生了进位/借位(无符号溢出)
  • OF(Overflow Flag):是否有符号溢出
CMP 通过减法更新 ZF SF CF OF 而不回写差值

这些标志用于后续的条件跳转,如 jejgjl 等。

移位

移位是一种常见的位运算操作,常用于实现快速的乘法、除法、符号扩展等功能。根据处理方式不同,移位可分为 逻辑移位算术移位循环移位三类:

  • 逻辑移位(Logical Shift):用于 无符号整数,空位统一用 0 填充。
  • 算术移位(Arithmetic Shift):用于 有符号整数,右移时保持符号位不变。
  • 循环移位(Rotate Shift):将移出的位补回另一端,不丢失任何一位。
逻辑移位、算术移位和循环移位的补位与移出规则

常见的移位指令如下表所示:

指令 含义 说明
SHL/SAL 左移(逻辑/算术) 低位补 0;不溢出时相当于乘以 2k2^k
SHR 逻辑右移 高位补 0,用于无符号数
SAR 算术右移 高位补符号位,用于有符号补码
ROL 循环左移 最高位移出后补入最低位
ROR 循环右移 最低位移出后补入最高位

逻辑移位

逻辑移位适用于 无符号整数,移位时将空出的位补为 0,不考虑操作数的符号。

  • 逻辑左移 (SHL):整体向左移动,低位补 0,高位移出丢弃;
  • 逻辑右移 (SHR):整体向右移动,高位补 0,低位移出丢弃。
; 逻辑移位的实例
mov al, 10000000b  ; 原值 -128(补码表示)
shr al, 1          ; 结果变为 01000000b,即十进制 64

尽管原数是负数,但使用 SHR 逻辑右移时,仍然将高位补 0,因此结果不再保留符号。

算术移位

算术移位适用于 有符号整数,在右移时会 保留符号位(最高位),使符号不变。对补码数,右移 kk 位通常相当于向负无穷方向取整的除以 2k2^k,因此负奇数时不一定等同于许多 ISA 有符号除法“向零截断”的结果。

  • 算术左移 (SAL):整体向左移动,低位补 0,高位移出丢弃;
  • 算术右移 (SAR):整体向右移动,最高位保持原符号位的值。
; 算术移位的实例
mov al, -16        ; 二进制补码:11110000(0xF0)
sar al, 1          ; 结果:11111000(0xF8)→ -8

由于保留了最高位 1,右移后结果仍为负数。

循环移位

循环移位(Rotate Shift)是一种将移出的位 重新从另一端补入 的移位方式,不改变位的总数,也不会丢弃任何一位,常用于加密、校验等需要 “保留所有信息” 的场景。

常见类型包括:

  • 循环左移 (ROL):将最高位移出后补入最低位;
  • 循环右移 (ROR):将最低位移出后补入最高位。
; 循环右移的实例
mov al, 10000001b  ; 原值:0x81
ror al, 1          ; 结果:11000000b(原最低位 1 补到了最高位)

与逻辑移位和算术移位不同,循环移位不引入新的位填充,因此所有位的内容只是位置发生变化,适用于 无符号与有符号数 的位模式操作,但不适合做乘除法运算。

控制转移指令

无条件跳转

无条件跳转到某个标签(label)。

标签是一个可识别的标识符,标签通常是一个有意义的名字,后跟一个冒号,用于标记程序中的某个位置或地址。

jmp label
JMP 以当前指令的下一条地址为基准计算相对偏移并更新 PC

当题目明确给出相对寻址时,跳转偏移量是相对于下一条指令的地址(即当前 PC + 指令长度)来计算的。

对于 jmp label 指令,若 label 在距离当前指令之后 N 字节处,则相对寻址的偏移量可以通过以下公式计算:

偏移量 = 标签地址 - (当前 PC 值 + 指令长度)
偏移量 = 标签地址 - (jmp 下一条指令地址)

注意:教材题目中的以下直接控制转移示例常可采用 PC 相对寻址说明:

JMP LABEL
BEQ LABEL
BNE LABEL
CALL LABEL

默认都可以理解为:

若题设指定相对寻址,控制转移目标按 PC 相对偏移计算。

但从计算机体系结构的角度来说,JMP 并不天然等于相对寻址,它也可以是绝对或间接跳转。

条件跳转

CMP 指令后常常跟一个 条件跳转 指令,条件跳转指令会检查标志寄存器(FLAGS)的标志,从而决定是否跳转到某个标签(条件成立时);如果不跳转,则继续顺序执行。

; 比较 eax 和 ebx 的值
cmp eax, ebx
; 执行条件跳转
je equal_label

条件跳转的过程可以通过下图进行辅助理解:

条件跳转读取 CMP 设置的条件标志决定更新 PC 或顺序执行

有符号数

对于有符号数 来说,存在以下条件跳转指令:

指令 全称 跳转条件
JE/JZ jump equal/zero(相等/零) ZF=1ZF=1
JNE/JNZ not equal/not zero(不等/非零) ZF=0ZF=0
JG greater(大于) ZF=0SF=OFZF=0\land SF=OF
JL less(小于) SFOFSF\ne OF
JGE greater or equal(大于等于) SF=OFSF=OF
JLE less or equal(小于等于) ZF=1SFOFZF=1\lor SF\ne OF

无符号数

对于无符号数 来说,存在以下条件跳转指令:

指令 全称 跳转条件
JA jump above(无符号大于) CF=0ZF=0CF=0\land ZF=0
JAE jump above or equal(无符号大于等于) CF=0CF=0
JB jump below(无符号小于) CF=1CF=1
JBE jump below or equal(无符号小于等于) CF=1ZF=1CF=1\lor ZF=1

注意:为什么针对有符号数和无符号数要有两套指令?

因为 CPU 不区分有符号数和无符号数,它根本不知道 cmp 指令比较的数字是 int 还是 unsigned。

所以指令集会提供两套跳转指令:

  • 编译器认为这是 signed → 用 JG/JL
  • 编译器认为这是 unsigned → 用 JA/JB

子程序调用

在汇编语言中,调用一个子程序通常使用 CALL 指令,执行完子程序后使用 RET 指令返回。两者配合,实现了 从主程序跳转到子程序,再返回继续执行 的控制流程。

call subroutine
...
subroutine:
    ; 执行一些操作
    ret

在 x86 的常见 CALL/RET 形式中,CALL 指令用于 调用子程序,涉及以下步骤:

  • 保存返回地址:将当前指令的下一个地址(即返回地址)压入栈中,这样子程序返回时才知道从哪一条指令继续执行。
  • 跳转到子程序:将程序计数器设置为子程序的入口地址,开始执行子程序的代码。

RET 指令用于 从子程序返回到调用函数,涉及以下步骤:

  • 从栈中弹出返回地址:从栈顶弹出一个值,并将这个值作为返回地址。这是之前 CALL 指令压入栈的地址。
  • 跳转到返回地址:将程序计数器设置为返回地址,继续执行从调用子程序的指令的下一条指令。

子程序调用的过程可以通过下图进行辅助理解:

CALL 压入返回地址并跳到子程序而 RET 弹出返回地址继续主程序

图的阅读顺序对应四个步骤:

  1. CALL 跳转:执行 call 0x2000,PC 被设为子程序入口地址 0x2000,控制流从主程序跳转过去。
  2. 压入返回地址:CALL 同时把下一条指令的地址(0x1008,即 call 后面那条)压入栈顶,SP 下移指向新的栈顶。
  3. 弹出返回地址:子程序执行完毕,ret 从栈顶弹出 0x1008,SP 上移恢复。
  4. RET 返回:PC 被设为弹出的 0x1008,控制流跳回主程序,从 call 的下一条指令继续执行,宛如什么都没发生过。

补充

以下三个知识点可以放在一起学习:

陷阱指令

显式陷阱指令(Trap Instruction)是一类由程序执行而触发的 同步控制转移指令。在操作系统正确配置入口和权限的前提下,它可触发从用户态到内核态的控制转移,以请求操作系统执行特权操作。它们本质上是一种软件触发机制,通常用于:

  • 实现 系统调用(如文件操作、进程控制等);
  • 支持 断点调试(例如 IDE 或 GDB 中设置断点);

除 0、非法访问等是 CPU 检测到的同步异常,通常走相近的异常入口机制,但并不是程序执行显式陷阱指令本身。把这两类情况区分开,才能准确解释“软件主动请求”和“硬件检测错误”。

陷阱的特点是由 程序主动触发,与硬件中断(如 I/O、时钟中断)区分开来。

INT

INT 指令用于产生一个软件中断,它后面跟着一个中断向量号(通常是一个字节大小的立即数),用于指定要调用的中断或服务例程:

INT n  ; n 为中断向量号(0~255)

执行后,CPU 根据中断向量号 nn 查找对应处理程序地址并跳转执行。传统 x86 实模式使用中断向量表(IVT),保护模式和长模式使用 IDT;408 的抽象模型可统称为中断/异常向量表。常见的历史用法如下:

mov eax, 1   ; 系统调用:exit
mov ebx, 0   ; 退出代码
int 0x80     ; 32 位 Linux 历史系统调用入口示例

补充

Trap 指令和 TF 标志位的区别

陷阱指令(如 INT) ≠ TF 标志位

  • INT 指令显式触发陷阱,由程序执行。
  • TF是 EFLAGS 寄存器中的一个位,设置为 1 后每执行一条指令会产生一次调试异常(#DB,向量 1),用于单步调试。
  • 二者都能进入内核态,但触发机制不同。

INT 是显式软件中断/陷阱的一种 x86 形式;TF 是调试控制标志。是否允许通过 POPF 等方式修改控制标志取决于当前特权级和处理器规则,不能把它当作任意用户程序都可用的开关。

中断允许与禁止指令

开关中断

CLISTI 用于控制 CPU 的 中断响应能力。具体来说,这两条指令用于修改处理器的 中断标志(IF,Interrupt Flag),从而控制外部硬件中断的使能和禁止。

中断标志(IF)是状态寄存器中的一个标志位。如果 IF 位为 1,处理器可以响应可屏蔽外部中断;如果 IF 位为 0,处理器暂不响应可屏蔽外部中断。IF 不影响不可屏蔽中断。

  • CLI:清除 中断标志位(Clear Interrupt Flag),将 IF 位设置为 0,从而禁止处理器响应外部硬件中断。
  • STI:设置 中断标志位(Set Interrupt Flag),将 IF 位设置为 1,从而允许处理器响应外部硬件中断。

在 x86 的受保护执行环境中,执行 CLI/STI 通常需要相应特权;普通用户态程序不能随意关闭系统的可屏蔽中断。408 题目若只给出抽象 IF,则按题设讨论其对中断响应的影响。

cli   ; 关闭中断
sti   ; 开启中断

补充

IF 位控制的是可屏蔽中断(IRQ),但不会影响不可屏蔽中断(NMI)。

输入输出

INOUT 指令用于处理与外部设备的 输入/输出(I/O)操作。这些指令让 CPU 可以直接与硬件端口通信,从而读取或发送数据。

独立编址 I O 中 CPU 通过 IN 从端口读取并通过 OUT 写入端口
  • IN:从指定的 I/O 端口读取数据到 寄存器。通过 IN 指令,可以从硬件设备读取状态信息或数据。

  • OUT将寄存器中的数据写入到指定的 I/O 端口。通过 OUT 指令,CPU 可以向设备发送控制命令或数据。

  • 语法

  • 实例

; 输入指令
in  reg, port      ; 从 I/O 端口 port 读取一个字节/字到 reg 寄存器

; 输出指令
out port, reg      ; 将 reg 寄存器中的值写入 I/O 端口 port
; 读取键盘控制器状态(端口 0x64)
in al, 0x64

; 将 AL 中的数据再次写回键盘控制器
out 0x60, al

注意 I/O 端口编址方式有两种:统一编址和独立编址,IN/OUT 指令仅适用于独立编址;对于统一编址,应使用体系结构提供的普通访存 load/store 指令(x86 示例中可表现为 MOV)完成输入输出。

输入输出指令(如 IN / OUT)常用于程序查询方式程序中断方式中,由 CPU 发出指令与设备进行数据交换。相比之下,DMA 方式则由硬件控制器自动完成传输,通常不依赖此类指令。

程序查询方式 和 程序中断方式 这两种 I/O 方式的实现的核心逻辑可以参考下述代码:

  • 程序查询方式
  • 中断方式
// 应用请求的抽象;实际端口访问由内核态驱动执行
write_to_device(data) {
    while (1) {
        if (kernel_read_port(STATUS_PORT) & READY_BIT) { // 轮询
            kernel_write_port(DATA_PORT, data);          // 实际执行 OUT
            break;
        }
    }
}

// 内核态驱动伪代码,用一个 C 函数封装了底层输入输出汇编指令
// 通过读取端口内容,获取设备状态
uint8_t kernel_read_port(uint16_t port) {
    asm("in %%dx, %%al" : "=a"(value) : "d"(port));
    return value;
}

// 通过端口向设备写入
void kernel_write_port(uint16_t port, uint8_t data) {
    asm("out %%al, %%dx" : : "a"(data), "d"(port));
}
// 用户程序只发起请求,挂起等待中断
read_from_device(buffer) {
    request_read();  // 向设备发送读取命令
}

// 内核:中断服务例程
interrupt_handler() {
    uint8_t data = inb(DATA_PORT);  // IN 指令读取设备数据
    kernel_buffer = data;
    wake_up_user();
}
  • 在程序查询方式中,CPU 通过反复执行 输入输出指令轮询设备状态,当设备准备就绪后再通过 输入输出指令执行读写操作。
  • 在中断方式中,当设备准备好数据后会向 CPU 发送中断信号;CPU 响应后进入中断服务程序,并在其中执行 输入输出指令与设备进行数据交换。

权限边界:上面的 IN/OUT 片段用于说明驱动层机制;在受保护操作系统中,应用程序通常通过系统调用或驱动 API 提交 I/O 请求,不能直接执行特权端口访问。

字符串操作指令

字符串操作指令用于成批移动、加载、存储或比较连续数据。以下采用传统 x86 的 ESI/EDI 命名;每次操作后指针按数据宽度递增或递减取决于方向标志 DF,使用 REP 前缀时还会由计数寄存器控制重复次数。

MOVS

复制字符串(ES:EDIDS:ESI)。

movs byte ptr es:[edi], byte ptr ds:[esi]  ; 复制 1 字节
movs dword ptr es:[edi], dword ptr ds:[esi]  ; 复制 4 字节

LODS

DS:ESI 加载数据到 AL/AX/EAX

lodsb  ; 读取 1 字节
lodsd  ; 读取 4 字节

STOS

AL/AX/EAX 存储到 ES:EDI

stosb  ; 存储 1 字节
stosd  ; 存储 4 字节

CMPS

比较两个字符串。

cmpsb  ; 比较字节
cmpsd  ; 比较 4 字节
MOVS LODS STOS CMPS 的源地址、目的地址、累加器与指针更新方向

总结

指令类别 代表指令 说明
数据传输 MOV 在寄存器、内存与立即数之间传送数据
堆栈操作 PUSH / POP 压入或弹出栈顶数据
加减运算 ADD / SUB 执行整数加减并更新相关标志
乘除运算 MUL / DIV 执行无符号乘除;有符号版本为 IMUL / IDIV
逻辑运算 AND / OR / XOR / NOT 按位逻辑操作
比较 CMP 只更新标志而不保存差值
无条件跳转 JMP 直接更新 PC
条件跳转 Jxx 根据条件标志选择目标 PC
子程序调用 CALL / RET 保存返回地址并调用或返回
输入/输出 IN / OUT 访问独立编址的 I/O 端口
字符串操作 MOVS / LODS / STOS / CMPS 操作连续内存数据
触发陷阱 INT 软件触发同步控制转移
控制可屏蔽中断 CLI / STI 清除或设置 IF 标志

标志位、控制转移与副作用的统一判断

算术逻辑指令的结果通常以固定字长截断保存,状态标志则保留结果的附加信息。零标志反映结果是否为零,符号标志反映结果最高位,进位标志反映无符号加减的进借位,溢出标志反映补码有符号结果是否超出可表示范围。进位和溢出不能混用:例如两个正补码数相加得到负号时可能溢出,即使无符号进位的取值并不相同。

比较指令可看作执行减法但不写回差值,只更新条件标志。无符号比较通常依据进位和零标志判断大小;有符号比较则需要结合符号位与溢出位,因为负数的最高位为 1 不代表它按无符号解释更小。看到条件跳转助记符时,应先问它描述的是有符号还是无符号关系,再根据前一条比较的操作数顺序判断跳转方向。

改写 PC 的三种语义

普通顺序执行按指令长度更新 PC;条件或无条件跳转把 PC 改为目标地址;子程序调用还需要保存“下一条指令”的返回地址,返回指令从保存位置恢复 PC。调用保存返回地址并不自动保存所有通用寄存器,哪些寄存器由调用方或被调方保存取决于调用约定。

软件中断或陷阱同样会改变控制流,却不等同于普通调用:处理器要按中断/异常入口机制保存必要状态并通过向量或表项进入处理程序,返回时恢复状态字和断点语义。开关可屏蔽中断的指令只影响可屏蔽外部请求,不会屏蔽所有异常或不可屏蔽中断。I/O 指令访问的是接口寄存器;在独立编址模型中端口空间与主存地址空间分开,在统一编址模型中则可用普通访存指令访问设备寄存器,二者的设备控制逻辑仍由 I/O 接口完成。