这是 8086 汇编中三个最容易混淆的返回指令。它们的区别在于从栈上弹出什么数据 以及如何恢复 CPU 执行状态。
核心区别一览表
| 指令 | 操作码 | 弹出字节数 | 弹出的内容 | 典型配对 | 使用场景 |
|---|---|---|---|---|---|
RET |
C3 |
2 | IP |
CALL NEAR |
段内返回 |
RETF |
CB |
4 | IP → CS |
CALL FAR |
段间返回 |
IRET |
CF |
6 | IP → CS → FLAGS |
INT n |
中断返回 |
详细分析
1. RET (Return from Near Call)
操作:
IP ← [SP]
SP ← SP + 2
特点:
-
只恢复指令指针 IP
-
不改变 CS(仍在同一代码段)
-
不恢复 FLAGS
对应调用: CALL NEAR(段内调用)
示例:
cpp
CALL NEAR func ; 压入 IP
...
func:
RET ; 弹出 IP,返回调用处
2. RETF (Return from Far Call)
操作:
cpp
IP ← [SP] ; 先弹出 IP
SP ← SP + 2
CS ← [SP] ; 再弹出 CS
SP ← SP + 2
特点:
-
恢复 IP 和 CS(实现段间跳转)
-
不恢复 FLAGS
对应调用: CALL FAR(段间调用)或手动 PUSH CS + PUSH IP + RETF
你的监控程序中的例子(源码第 552 行):
cpp
522 C287 A108FF MOV AX,[USER_CS] ; AX = 0000H
523 C28A 50 PUSH AX ; 压入 CS
524 C28B A106FF MOV AX,[USER_IP] ; AX = 0400H
525 C28E 50 PUSH AX ; 压入 IP
...(恢复其他寄存器)
552 C2B2 CB RETF ; 弹出 IP=0400, 弹出 CS=0000
; 跳转到 0000:0400(用户程序)
3. IRET (Return from Interrupt)
操作:
cpp
IP ← [SP] ; 弹出 IP
SP ← SP + 2
CS ← [SP] ; 弹出 CS
SP ← SP + 2
FLAGS ← [SP] ; 弹出 FLAGS
SP ← SP + 2
特点:
-
恢复 IP 、CS 和 FLAGS
-
通常还会恢复中断允许标志(IF)(因为
INT指令会自动清除 IF)
对应调用: INT n(软件中断)或硬件中断
栈帧对比图
执行 CALL NEAR 后的栈
cpp
执行 CALL func 前 执行 CALL func 后
SP → [旧数据] SP → [返回 IP(2 字节)]
SP+2 → [旧数据]
执行 CALL FAR 后的栈
cpp
执行 CALL FAR func 前 执行 CALL FAR func 后
SP → [旧数据] SP → [返回 IP(2 字节)]
SP+2 → [返回 CS(2 字节)]
SP+4 → [旧数据]
执行 INT n 后的栈
cpp
执行 INT n 前 执行 INT n 后
SP → [旧数据] SP → [返回 IP(2 字节)]
SP+2 → [返回 CS(2 字节)]
SP+4 → [FLAGS(2 字节)]
SP+6 → [旧数据]
常见错误
❌ 用 RET 代替 IRET:会导致 FLAGS 没有被弹出,栈不平衡,返回地址错误
❌ 用 IRET 代替 RET:会多弹出 2 字节当作 FLAGS,导致 IP 和 CS 错位
❌ 用 RETF 代替 IRET:FLAGS 没有被恢复,中断标志可能错误
✅ 正确原则:用什么指令调用,就用对应的指令返回
-
CALL NEAR→RET -
CALL FAR→RETF -
INT n→IRET -
