汇编:操作符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 不变);
    • 段间跳转:同时修改 CSIP

二、指令分类与寻址方式

寻址方式格式说明
段内短跳转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 编码对应寄存器
    REG000001010011100101110111
    寄存器AXCXDXBXSPBPSIDI

示例如下:

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 操作码内存位移量(小端存储)
26HFFH0000H(小端: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
    ModRegR/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 值对应指令
    000INC
    001DEC
    010CALL
    011CALL
    100JMP(段内跳转,word ptr)
    101JMP(段间跳转,dword ptr)
    110PUSH
    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 位偏移量)。
  • 执行逻辑
    1. 检测 CX 寄存器的内容是否为 0;
    2. CX = 0:IP = IP + 8 位偏移量(跳转到目标标号);
    3. CX ≠ 0:不跳转,执行下一条指令。
  • 对标志位的影响:无(不修改 FLAGS 寄存器的任何标志)。

下面举例演示:

二、指令本质(机器码层面)

JCXZ 是 8086 的专用条件跳转指令,等价于以下逻辑(但效率更高):

CMP CX, 0   ; 比较CX与0(仅影响标志位)
JE  目标标号; 若ZF=1(CX=0)则跳转

JCXZ 无需显式执行CMP,直接检测 CX 的值,减少指令数。


call指令

CALL 指令专用于调用子程序(过程 / 函数),核心逻辑是:

  1. 保存返回地址:将当前指令指针 IP(段内调用)或 CS:IP(段间调用)压入栈;
  2. 跳转至子程序入口:修改 IP(段内)或 CS:IP(段间),执行子程序;
  3. 子程序通过 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=0000HCS: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 汇编中专门用于无符号数乘法的指令,支持字节、字两种操作数长度,结果会根据操作数长度自动扩展到双倍长度(字节乘法结果为字,字乘法结果为双字)。

一、格式说明

  1. 操作数类型:仅支持无符号整数(有符号乘法用 IMUL);
  2. 隐含操作数:乘法的一个操作数固定为累加器(AL/AX),另一个为显式操作数(寄存器 / 内存);
  3. 结果存储结果自动存放在 AX(字节乘法)或 DX:AX(字乘法)中(DX 存高位,AX 存低位);
  4. 标志位影响:仅影响 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

posted @ 2025-12-06 19:09  chenlight  阅读(14)  评论(0)    收藏  举报  来源