← 返回 计组与微机控制

计组与微机控制

7.3 数据传送指令

7.3 数据传送指令

具体指令:

一. 按操作数个数分类

按照汇编指令格式,可以分为三类:

双操作数指令

有两个操作数:

MOV dest, src ADD dest, src CMP dest, src

例如:MOV AX, BX

其中:dest = AX src = BX

单操作数指令

只有一个操作数:INC AX DEC BX PUSH AX POP AX MUL BL

有时候这个操作数既是源,也是目的。

比如:INC AX

意思是:AX = AX + 1

AX 既提供原值,又接收结果。

无操作数指令

指令表面上没有写操作数,但可能隐含使用某些寄存器或标志位。

例如:CLC STC NOP XLAT

比如 XLAT 表面没有操作数,但隐含用 BX 和 AL。

二、数据传送类指令

数据传送类指令的作用是:把数据从一个地方传到另一个地方

传送的数据可以是:变量内容 变量地址标志寄存器内容 I/O 端口数据

注意,数据传送类指令一般不影响标志位。

课本说除了:SAHF POPF

之外,其余数据传送类指令执行后一般不影响标志寄存器。

三、数据传送类指令总表

课本表 4.7 把它们分成几类:

7.3 数据传送指令 图 1

通用数据传送:MOV、XCHG、XLAT、PUSH、POP地址传送:LEA、LDS、LES标志位传送:LAHF、SAHF、PUSHF、POPFI/O 数据传送:IN、OUT

四、MOV 指令

MOV 是最常用的数据传送指令。

格式:MOV dest, src

功能:dest ← src也就是把源操作数的内容送到目的操作数。

注意:源操作数不变 目的操作数被覆盖

例如:MOV AX, BX

执行后:AX = BXBX 不变

1. MOV 可以传字节,也可以传字

课本说,MOV 的两个操作数可以是字节,也可以是字。

但是二者必须等长。

合法:MOV AL, BL MOV AX, BX

不合法:MOV AX, BL MOV AL, BX

一个是 16 位,一个是 8 位,长度不一致。

MOV 的常见形式:MOV 的五种形式。

1. 立即数送寄存器

MOV reg, imm

例如:MOV AX, 0B123H MOV AL, 12H

意思是把立即数送入寄存器。(首位ABCDEF前面必须加0,要不会被认为是符号名)

注意立即数只能作为源操作数,不能作为目的操作数。

可以:MOV AX, 1234H 不可以:MOV 1234H, AX

2. 寄存器 / 段寄存器之间传送

MOV reg/sreg, reg MOV reg, sreg

例如:MOV DL, CL MOV DS, AX

这里要注意,段寄存器不能直接用立即数赋值。

不能写:MOV DS, 2000H

通常要通过通用寄存器中转:

MOV AX, 2000H MOV DS, AX

3. 立即数送内存

MOV mem, imm例如:MOV [BP + DI], 3210H

意思是把立即数 3210H 写到内存单元中。

如果内存操作数的大小不明确,要用 BYTE PTR 或 WORD PTR 指明。

比如:

MOV BYTE PTR [DI], 12H MOV WORD PTR [DI], 1234H

4. 寄存器 / 段寄存器送内存

MOV mem, reg/sreg

例如:MOV [BX + SI], AX表示把 AX 写入内存。

教材还写了类似:MOV [CX + 2], ES

但按 8086 寻址规则,CX 不能作为存储器寻址寄存器。真正合法的基址/变址寄存器是 BX、BP、SI、DI。有些老教材或印刷示例可能存在简化或不严谨,考试以老师给的规则为准。

5. 内存送寄存器 / 段寄存器

MOV reg/sreg, mem

例如:

MOV CX, BUFFER MOV DS, [BX]

意思是从内存取数据送到寄存器或段寄存器。

五、使用 MOV 的几个限制

课本列了很重要的规则。

1. 立即数只能作为源操作数

合法:MOV AX, 1234H 不合法:MOV 1234H, AX

因为立即数是常数,不能被写入。

2. CS 只能作为源操作数,不能作为目的操作数

也就是说可以读 CS:MOV AX, CS

但不能直接写:MOV CS, AX

因为 CS 控制当前代码段,如果随便 MOV 修改 CS,程序执行流会混乱。

要改变 CS,通常通过:

JMP FAR CALL FAR RET FAR INTIRET

这些控制转移指令实现。

3. 源操作数和目的操作数不能同时是内存

不能写:MOV [2000H], [1000H]

因为 8086 普通双操作数指令不允许内存到内存。

要通过寄存器中转:

MOV AX, [1000H] MOV [2000H], AX

4. 立即数不能直接送段寄存器

不能:MOV DS, 2000H

应该:MOV AX, 2000H MOV DS, AX

5. 不同段寄存器之间不能直接传送

比如不能:MOV DS, ES

通常也要通过通用寄存器:

MOV AX, ES MOV DS, AX

6. 源和目的长度必须一致

合法:MOV AX, BX MOV AL, BL

不合法:MOV AX, BL MOV AL, BX

如果内存操作数大小不明确,要写:

BYTE PTR WORD PTR DWORD PTR

例如:MOV BYTE PTR LABEL, BL MOV WORD PTR [DI], AX

六、例 4.3:把 DATA 的内容送 DS 和 ES

课本例子是:

MOV AX, DATA MOV DS, AX MOV ES, AX

思路是:不能直接 MOV DS, DATA也不能直接 MOV ES, DATA

所以先把 DATA 送到 AX,再由 AX 送到 DS 和 ES。

也就是说 AX 充当中间寄存器。

这个模式很常见:MOV AX, 数据段段地址MOV DS, AX

初始化 DS 时经常这样写。

七、XCHG 交换指令

XCHG oprd1, oprd2

功能是:oprd1 ↔ oprd2

也就是两个操作数内容互相交换。

例如:XCHG AX, BX

执行前:AX = 1111H BX = 2222H

执行后:AX = 2222H BX = 1111H

1. XCHG 不影响标志位

XCHG 只是交换数据,不做加减逻辑判断,所以不会影响 FLAGS。

2. XCHG 的合法形式

课本列:

XCHG reg, reg XCHG mem, reg XCHG reg, mem

例如:

XCHG AX, BX XCHG [ADDR], BX XCHG BX, [BP + SI]

但是不能:XCHG mem, mem 也不能:XCHG reg, imm

3. 两个内存单元之间怎么交换?

例 4.4 讲这个问题。

如果要交换两个内存单元 ADD1 和 ADD2 的内容,不能直接写:XCHG ADD1, ADD2

因为两个都是内存操作数。要用寄存器中转:

MOV AL, ADD1 XCHG AL, ADD2 XCHG AL, ADD1

我们假设:ADD1 = 11H ADD2 = 22H

第一条:MOV AL, ADD1

执行后:AL = 11H ADD1 = 11H ADD2 = 22H

第二条:XCHG AL, ADD2

执行后:AL = 22H ADD2 = 11H ADD1 = 11H

第三条:XCHG AL, ADD1

执行后:AL = 11H ADD1 = 22H ADD2 = 11H

最终 ADD1 和 ADD2 交换成功。

八、XLAT 查表转换指令

XLAT它是 Translate,查表转换指令。

格式表面上没有操作数:XLAT 但它隐含使用:BX 和 AL

功能是:AL ← [BX + AL]

更完整地说:AL ← DS:[BX + AL]

1. XLAT 的作用

XLAT 用来根据 AL 中的下标,到表中查一个字节,再把查到的值放回 AL。

可以把它理解成 C 语言里的:

AL = table[AL];

其中:

BX = table 的首地址AL = 要查的下标

执行后:

AL = 表中对应项的值BX 不变

2. 使用 XLAT 的步骤

一般流程是:

MOV BX, OFFSET TABLE MOV AL, 下标XLAT

执行后,AL 就变成查表结果。

3.例 4.5:BCD 码转 7 段 LED 显示码

课本说,数字 0~9 对应的七段显示码放在一张表中。

例如表中可能是:

数字 0 → 40H数字 1 → 79H数字 2 → 24H数字 3 → 30H数字 4 → 19H...

如果要查数字 4 对应的显示码:

MOV BX, OFFSET HEX_TABLE MOV AL, 4XLAT

执行过程:

BX = 表首地址AL = 4CPU 访问 DS:[BX + 4]把该单元内容送入 AL

如果表中第 4 项是 19H,那么执行后:AL = 19H

这就完成了从数字 4 到七段码 19H 的转换。

九、PUSH 和 POP 堆栈操作指令(老生常谈之前说过好多次了)

堆栈是先进后出结构:后放进去的,先取出来

8086 的堆栈以字为单位操作,也就是每次 PUSH/POP 都是 16 位,两个字节。

PUSH 指令格式:

PUSH src功能:把 16 位源操作数压入堆栈

课本给出的过程是:

SP ← SP - 2[SP + 1 : SP] ← src

更容易理解:先让 SP 减 2,再把一个字写入 SS:SP 指向的栈顶

为什么 SP 要减 2?因为 8086 堆栈向低地址方向增长,并且每次压入一个字,占 2 个字节。

1. PUSH 的小端存放(栈从下面往上生长,也就是一开始指针在栈底)

假设:AX = 1234H SS = 3000H SP = 1000H

执行:PUSH AX

第一步:

SP = SP - 2 = 0FFEH

第二步,把 AX 写入 SS:0FFEH 开始的两个字节:

SS:0FFEH = 34HSS:0FFFH = 12H

所以低字节 34H 放低地址,高字节 12H 放高地址。

执行后:SP = 0FFEH

二十六、POP 指令

格式:POP dest

功能:从栈顶弹出一个 16 位数据送到目的操作数

过程是:

dest ← [SP + 1 : SP]SP ← SP + 2

更容易理解:先从 SS:SP 取一个字,再让 SP 加 2

1. POP 例子

接着刚才的例子,栈顶有:

SS:0FFEH = 34HSS:0FFFH = 12HSP = 0FFEH

执行:POP BX

CPU 从 SS:0FFEH 和 SS:0FFFH 取一个字:BX = 1234H

然后:SP = SP + 2 = 1000H

这就完成弹栈。

PUSH/POP 的本质

可以这样记:PUSH:SP 先减 2,再写入数据POP:先读出数据,SP 再加 2

方向:堆栈向低地址增长(也就是默认栈底在高地址,也就是下面,栈顶在低地址,也就是上面) PUSH 让 SP 变小POP 让 SP 变大

这个在 CALL、RET、中断处理中非常重要。

因为调用子程序时,返回地址就是通过堆栈保存的。

总结:这几页最核心的是两块:

1. 寻址方式补全

寄存器相对寻址:EA = BX/BP/SI/DI + disp基址变址相对寻址:EA = BX/BP + SI/DI + dispI/O 直接端口寻址:端口号直接写在指令中,范围 00H~FFHI/O 间接端口寻址:端口号放在 DX 中,范围 0000H~FFFFH隐含寻址:操作数不写出来,但 CPU 默认使用某些寄存器

2. 数据传送指令开头

MOV:传送XCHG:交换XLAT:查表转换PUSH:压栈POP:出栈

特殊记忆:

MOV 不能内存到内存MOV 不能立即数直接送段寄存器CS 不能作为 MOV 的目的操作数XCHG 不能内存和内存直接交换XLAT 隐含 BX 和 AL,功能是 AL ← [BX + AL] PUSH 先 SP-2 再写栈POP 先读栈再 SP+2

这里开始,汇编味道就很重了。后面继续就是 PUSH/POP 的具体形式,以及 LEA、LDS、LES、LAHF、SAHF、PUSHF、POPF、IN、OUT 等指令