汇编:操作符offset、jmp指令、jcxz指令、call指令和ret指令、mul指令
OFFSET 是伪指令(伪操作符)(而非处理器执行的指令),核心作用是获取标识符(变量、标号、段名等)的偏移地址(即相对于所在段起始地址的偏移量),本质是汇编器在编译阶段计算并替换的常量值。
一、核心概念
汇编程序中,内存地址由「段地址(段寄存器)+ 偏移地址」组成:
- 段地址:段寄存器(如
CS/DS/ES)指向段的起始地址; - 偏移地址:标识符到段起始地址的字节数(
OFFSET就是获取这个值)。
OFFSET 仅在汇编阶段生效,汇编器会直接计算偏移量并替换到代码中,运行时无额外开销。
二、语法格式
MOV 寄存器, OFFSET 标识符 ; 将标识符的偏移地址送入寄存器
; 或者是
MOV 内存单元, OFFSET 标识符 ; 将偏移地址存入内存(少用)
三、示例
DATA SEGMENT ; 定义数据段
num DB 10H ; 字节变量,偏移地址 0000H
str DB 'ABC' ; 字节数组,偏移地址 0001H
arr DW 1,2,3 ; 字数组,偏移地址 0004H
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE, DS:DATA ; 关联段寄存器与段
START:
MOV AX, DATA ; 数据段地址送入AX(8086不能直接给DS赋值)
MOV DS, AX ; DS指向数据段
; 1. 获取num的偏移地址(0000H)送入BX
MOV BX, OFFSET num ; 汇编后等价于 MOV BX, 0000H
MOV AL, [BX] ; AL = 10H(通过偏移地址访问num)
; 2. 获取str的偏移地址(0001H)送入SI
MOV SI, OFFSET str ; SI = 0001H
MOV DL, [SI+1] ; DL = 'B'(访问str的第二个字符)
; 3. 获取arr的偏移地址(0004H)送入DI
MOV DI, OFFSET arr ; DI = 0004H
MOV CX, [DI+2] ; CX = 2(arr的第二个元素,字占2字节)
MOV AH, 4CH
INT 21H ; 程序退出
CODE ENDS
END START
再例举一个示例:

assume cs:code
code segment
start:
mov ax,offset start
s: mov ax,offset s
code ends
end start
jmp指令
无条件转移,可以只修改IP,也可以同时修改CS和IP,用于直接修改程序计数器(CS:IP)的值,使程序跳转到指定地址执行,无任何条件限制。
一、核心原理
8086 的指令执行依赖 CS(代码段寄存器)和 IP(指令指针寄存器):
CS存储代码段的段基址(左移 4 位);IP存储段内偏移地址;JMP指令的本质是修改CS和 / 或IP:- 段内跳转:仅修改
IP(CS 不变); - 段间跳转:同时修改
CS和IP。
- 段内跳转:仅修改
二、指令分类与寻址方式
| 寻址方式 | 格式 | 说明 |
|---|---|---|
| 段内短跳转 | JMP SHORT 标号 | 短跳转:偏移量为 8 位有符号数(范围:-128 ~ +127),节省空间 |
| 段内近跳转 | JMP NEAR PTR 标号 | 近跳转:偏移量为 16 位有符号数(范围:-32768 ~ +32767),默认段内跳转 |
| 段内寄存器寻址 | JMP REG | 跳转目标偏移地址直接存放在 16 位寄存器中(如 JMP AX) |
| 段内间接寻址 | JMP WORD PTR [BX] | 从内存单元(字单元)中读取偏移地址,赋值给 IP |
2.1短转移(Short Jump):范围 -128 ~ +127 字节
- 用途:短距离跳转,指令长度 2 字节;
- 编码格式:
第 1 字节(操作码) 第 2 字节(偏移量) 11101011(EB)disp8(8 位带符号偏移量) - 偏移量计算:
disp8 = 目标IP - (当前IP + 2)。
下面举例说明偏移量的计算方法

再举例验证

上述两例均验证了,jmp short s的机器指令中,如EB03/EB05,包含的是跳转到指令的相对位置,而不是转移的目标地址。
2.2 近转移(Near Jump):范围 -32768 ~ +32767 字节
- 用途:段内长距离跳转,指令长度 3 字节;
- 编码格式:
第 1 字节(操作码) 第 2 字节(偏移低 8 位) 第 3 字节(偏移高 8 位) 11101001(E9)disp16低8位disp16高8位 - 偏移量计算:
disp16 = 目标IP - (当前IP + 3)(16 位带符号数,小端存储)。

2.3 远转移(Far Jump):整个 1MB 地址空间(8086 的寻址范围:00000H ~ FFFFFH)
jmp far ptr 是 8086 汇编中远转移指令,属于段间转移(跨段跳转),会同时修改 CPU 的 CS(代码段寄存器)和 IP(指令指针寄存器),实现从当前代码段跳转到任意其他代码段的指令执行。
语法如下:
jmp far ptr 目标标号 ; 汇编器自动解析标号的段地址(CS)和偏移地址(IP)
; 或等价的机器码形式(手动指定段和偏移)
jmp 段地址:偏移地址 ; 如 jmp 0x1000:0x0000


由上可见 ,far ptr 跳转所指向的0E26:010B处,就是add ax,1
| 远转移jmp far ptr 标号 | 近转移jmp near ptr 标号 |
| 段间转移 | 段内转移 |
| 直接指明了跳转到目标地址,即包含了标号的段地址CS和偏移地址IP | 指明的是相对于当前IP的转移位移,不是转移的目的地址 |
2.4 通过寄存器转移(如 JMP BX)
跳转目标 IP 从通用寄存器或内存单元获取(CS 不变)。
- 编码格式:
操作码 MOD+REG+R/M 11101101(FF)11 reg 111(MOD=11 表示寄存器,R/M=111 无效,REG 指定寄存器) - REG 编码对应寄存器:
REG 000 001 010 011 100 101 110 111 寄存器 AX CX DX BX SP BP SI DI
示例如下:

2.5 通过内存转移(如 JMP WORD PTR [BX])
列举一个示例
assume cs:code
code segment
start:
mov ax,0123H
mov ds:[0],ax
jmp word ptr ds:[0]
code ends
end start
解读如下:
JMP word ptr ds:[0]
这是 段内间接短跳转(仅修改 IP,不修改 CS),寻址方式为「直接内存寻址(位移量 0)+ 段超越前缀 ds」。
8086 中,该指令的机器码结构分为三部分:
| 段超越前缀 | JMP 操作码 | 内存位移量(小端存储) |
|---|---|---|
26H | FFH | 0000H(小端:00 00) |
8086 规定了不同段寄存器的超越前缀编码:
- DS:
26H - ES:
27H - SS:
36H - CS:
2EH - 这里指令显式指定
ds:[0],因此必须加26H作为段超越前缀。

再列举一个示例:
assume cs:code
code segment
start:
mov ax,0123H
mov [bx],ax
jmp word ptr [bx]
code ends
end start
8086 中,JMP 指令的机器码由操作码字节 + ModR/M 字节 组成(无位移量时仅 2 字节)
- 操作码(Opcode):段内跳转且寻址方式为
[寄存器]时,JMP的基础操作码是FF(二进制11111111); - ModR/M 字节:用于编码「寻址方式(Mod)」和「寄存器 / 内存操作数(R/M)」,格式为:
位 7-6 位 5-3 位 2-0 Mod Reg R/M
下面拆解ModR/M:
1. Mod 位(位 7-6):00
Mod 字段用于表示「寻址方式是否带位移量」:
00= 无位移量(且如果 R/M 对应110则是直接寻址,否则是寄存器间接寻址);- 此处 R/M 不是
110,因此是纯寄存器间接寻址(无 8 位 / 16 位位移量)。
2. Reg 位(位 5-3):100
Reg 字段用于指定「指令的扩展操作码」(因基础操作码FF是通用操作码,需 Reg 字段区分具体指令):
- 对于操作码
FF,Reg 字段的编码规则:Reg 值 对应指令 000 INC 001 DEC 010 CALL 011 CALL 100 JMP (段内跳转,word ptr) 101 JMP (段间跳转,dword ptr) 110 PUSH 111 保留 - 此处是
JMP word ptr [bx](段内跳转),因此 Reg 字段必须为100。
3. R/M 位(位 2-0):111
R/M 字段用于指定「内存寻址的寄存器」,结合 Mod=00时的规则:
| R/M 值 | Mod=00 时的寻址方式 |
|---|---|
| 000 | [BX+SI] |
| 001 | [BX+DI] |
| 010 | [BP+SI] |
| 011 | [BP+DI] |
| 100 | [SI] |
| 101 | [DI] |
| 110 | 直接寻址([16 位偏移]) |
| 111 | [BX] |
- 指令中是
[BX],因此 R/M 字段为111。
拼接 ModR/M 字节
- Mod(00) + Reg(100) + R/M(111) = 二进制
00100111= 十六进制27H。

jcxz指令
JCXZ(Jump if CX is Zero)是 8086 汇编中以 CX 寄存器为条件的短跳转指令,核心功能是:检查 CX 寄存器的值是否为 0,若为 0 则跳转到目标地址;若不为 0 则继续执行下一条指令。
一、指令基本格式
JCXZ 目标标号 ; 等价于:if (CX == 0) goto 目标标号;
- 操作数:只能是短标号(Short Label),跳转范围为当前 IP 的
-128 ~ +127字节(8 位偏移量)。 - 执行逻辑:
- 检测 CX 寄存器的内容是否为 0;
- 若
CX = 0:IP = IP + 8 位偏移量(跳转到目标标号); - 若
CX ≠ 0:不跳转,执行下一条指令。
- 对标志位的影响:无(不修改 FLAGS 寄存器的任何标志)。
下面举例演示:

二、指令本质(机器码层面)
JCXZ 是 8086 的专用条件跳转指令,等价于以下逻辑(但效率更高):
CMP CX, 0 ; 比较CX与0(仅影响标志位)
JE 目标标号; 若ZF=1(CX=0)则跳转
但 JCXZ 无需显式执行CMP,直接检测 CX 的值,减少指令数。
call指令
CALL 指令专用于调用子程序(过程 / 函数),核心逻辑是:
- 保存返回地址:将当前指令指针
IP(段内调用)或CS:IP(段间调用)压入栈; - 跳转至子程序入口:修改
IP(段内)或CS:IP(段间),执行子程序; - 子程序通过
RET指令弹出栈中返回地址,恢复执行流程。
| ret 指令 | retf指令 | |
| 功能 | 用栈中的数据,修改IP的内容,从而实现近转移 | 用栈中的数据,修改CS和IP的内容,从而实现远转移 |
| 相当于 | pop IP |
pop IP pop CS |
下面举例说明ret指令,并进行解释:
assume cs:code,ss:stack ; 伪指令:告知汇编器cs关联code段、ss关联stack段
stack segment ; 栈段定义(16字节,初始全0)
db 16 dup (0) ; 分配16字节空间,初始值0
stack ends
code segment ; 代码段定义
mov ax,4c00H ; 程序正常退出指令(INT 21H 4C号功能)
int 21H ; 但这段代码不会被直接执行(关键!)
start: ; 程序入口(end start 指定)
mov ax,stack ; 初始化栈段寄存器SS
mov ss,ax
mov sp,16 ; 设置栈顶指针SP=16(栈底是stack:0,栈顶初始指向stack:16)
mov ax,0 ; AX赋值0
push ax ; 将AX(0)压栈:栈中sp从16→14,stack:14~15存储0000H
mov bx,0 ; BX赋值0(无实际作用,仅占位)
ret ; 关键指令:ret = pop IP → 从栈中弹出值到IP寄存器
code ends
end start ; 汇编结束,指定程序入口为start标签
步骤 1:程序启动,CS:IP 指向start
- DOS 加载程序后,
CS指向code段基址,IP=偏移地址(start)(即mov ax,stack的偏移)。
步骤 2:初始化栈
mov ax,stack ; AX = stack段的段基址
mov ss,ax ; SS = stack段基址(栈段寄存器赋值必须通过AX中转)
mov sp,16 ; 栈顶SP=16(栈空间是stack:0~15,SP=16表示栈空)
步骤 3:压栈操作
mov ax,0 ; AX=0
push ax ; 栈操作:SP -= 2 → SP=14,将AX(0000H)存入stack:14(低字节)和stack:15(高字节)
步骤 4:ret指令改写 IP
ret指令的本质是:pop IP(从栈中弹出 2 字节到指令指针 IP)。
- 执行
ret前:栈顶(SP=14)存储的是0000H; - 执行
ret后:- SP += 2 → SP=16(栈恢复为空);
- IP 被赋值为
0000H(从栈中弹出的值);
步骤 5:程序跳转到code:0000H执行
IP=0000H → CS:IP指向code段偏移 0 的位置,即:
mov ax,4c00H
int 21H
这是 DOS 的程序退出中断(4C 号功能),执行后程序正常退出。
下面举例说明retf指令,并进行解释:
assume cs:code,ss:stack ; 伪指令:告知汇编器CS关联code段、SS关联stack段
stack segment ; 定义栈段(16字节,初始值全0)
db 16 dup (0) ; 分配16字节空间,每个字节初始化为0
stack ends ; 栈段结束
code segment ; 代码段开始
; 程序退出指令(先定义,后通过retf跳转执行)
mov ax,4c00H
int 21H
start: ; 程序入口(end start指定)
; 初始化栈寄存器
mov ax,stack
mov ss,ax
mov sp,16 ; 栈顶指向stack段的最高地址(栈段大小16字节,sp=16表示栈空)
mov ax,0 ; ax清零(无实际作用,可忽略)
; 压栈:为retf准备返回地址(栈中顺序:先压CS,后压IP)
push cs
push ax
mov bx,0 ; bx清零(无实际作用,可忽略)
retf ; 远返回:弹出IP→0,弹出CS→原CS(代码段)
code ends ; 代码段结束
end start ; 汇编结束,指定程序入口为start标签
核心指令执行流程(关键是retf)
retf(远返回)的执行规则:
- 从栈中弹出低 2 字节到
IP寄存器; - 从栈中弹出高 2 字节到
CS寄存器; - 程序跳转到
CS:IP指向的地址执行。
mul指令
MUL(Unsigned Multiplication,无符号乘法)是 8086 汇编中专门用于无符号数乘法的指令,支持字节、字两种操作数长度,结果会根据操作数长度自动扩展到双倍长度(字节乘法结果为字,字乘法结果为双字)。
一、格式说明
- 操作数类型:仅支持无符号整数(有符号乘法用 IMUL);
- 隐含操作数:乘法的一个操作数固定为累加器(AL/AX),另一个为显式操作数(寄存器 / 内存);
- 结果存储:结果自动存放在 AX(字节乘法)或 DX:AX(字乘法)中(DX 存高位,AX 存低位);
- 标志位影响:仅影响 CF、OF(SF、ZF、AF、PF 无定义):
- 若结果的高位部分(AH 或 DX)为 0 → CF=0、OF=0;
- 若高位部分非 0 → CF=1、OF=1(表示结果需要高位存储)。
二、指令格式与分类
MUL 指令分两种格式,对应字节和字乘法:
| 指令格式 | 操作数类型 | 乘法运算 | 结果存储 | 高位部分 |
|---|---|---|---|---|
MUL reg8/mem8 | 字节(8 位) | AX = AL × 源操作数 | AX(16 位) | AH |
MUL reg16/mem16 | 字(16 位) | DX:AX = AX × 源操作数 | DX:AX(32 位) | DX |
注:源操作数不能是立即数,只能是 8/16 位寄存器或内存单元。
三、具体示例
1. 字节乘法(8 位 × 8 位 → 16 位)
需求:计算无符号数 50(0x32) × 20(0x14),结果存 AX。
MOV AL, 50 ; AL = 0x32(被乘数)
MOV BL, 20 ; BL = 0x14(乘数)
MUL BL ; AX = AL × BL = 0x32 × 0x14 = 0x448(十进制 1000)
; 执行后:AX=0x0448(AH=0x04,AL=0x48),CF=1、OF=1(AH≠0)
2. 字乘法(16 位 × 16 位 → 32 位)
需求:计算无符号数 1000(0x03E8) × 2000(0x07D0),结果存 DX:AX。
MOV AX, 1000 ; AX = 0x03E8(被乘数)
MOV BX, 2000 ; BX = 0x07D0(乘数)
MUL BX ; DX:AX = 0x03E8 × 0x07D0 = 0x000186A0(十进制 2000000)
; 执行后:DX=0x0001,AX=0x86A0,CF=1、OF=1(DX≠0)
3. 内存操作数示例
需求:计算内存单元 NUM_BYTE(8 位)与 AL 的乘积:
NUM_BYTE DB 30 ; 定义字节变量,值为30
...
MOV AL, 15 ; AL=15
MUL NUM_BYTE ; AX = 15 × 30 = 450(0x01C2),AH=0x01,AL=0xC2

浙公网安备 33010602011771号