← 返回 计组与微机控制

计组与微机控制

7.4 数据传送指令续

7.4 数据传送指令续

一、PUSH / POP 的几种操作数形式

书上这一页先总结 PUSH/POP 可以操作哪些对象:

PUSH/POP reg16PUSH/POP mem16PUSH/POP Sreg

也就是说,8086/8088 的栈操作一般是按字操作,一次压入或弹出 16 位,也就是 2 个字节。

1. PUSH/POP reg16

例如:PUSH AX POP AX

PUSH AX 是把 AX 的 16 位内容压入堆栈。

过程是:SP ← SP - 2SS:SP ← AX

也就是先让 SP 减 2,再把 AX 写入新的栈顶。

POP AX 是从栈顶弹出一个字送到 AX。

过程是:AX ← SS:SPSP ← SP + 2

也就是先取栈顶数据,再让 SP 加 2。

2. PUSH/POP mem16

例如:PUSH WORD PTR [BX] POP WORD PTR [DI]

这里操作数在内存中,但是依然必须是 16 位。

例如:PUSH WORD PTR [BX]

意思是把 DS:[BX] 开始的一个字压入堆栈。

如果内存中:DS:[BX] = 34HDS:[BX + 1] = 12H

那么这个字就是:1234H

压栈时会把 1234H 压入栈。

3. PUSH/POP Sreg

Sreg 是段寄存器,例如:

PUSH DS POP DS PUSH ES POP ES

段寄存器也是 16 位,所以可以入栈、出栈。

但是书上特别提醒:PUSH 可以使用 CS,POP 不允许使用 CS

也就是说:PUSH CS可以。但:POP CS不允许。

原因还是之前说过的:CS 控制当前代码段,不能用普通数据传送方式随便改。修改 CS 一般要靠 FAR JMP、FAR CALL、RET、INT、I RET 这些控制转移指令。

二、例 4.6:PUSH AX 和 POP AX 的执行过程

书上给出:AX = 1234H

然后看:PUSH AX POP AX

1. PUSH AX

假设执行前:

AX = 1234HSP 指向当前栈顶

执行:PUSH AX

第一步:SP ← SP - 2因为 8086 栈向低地址方向增长。

第二步,把 AX 写入 SS:SP 指向的新栈顶。

由于 8086 是小端方式,所以:

低地址放低字节高地址放高字节

AX = 1234H,所以:

SS:SP ← 34HSS:SP + 1 ← 12H

也就是低字节 34H 先放,高字节 12H 后放。

2. POP AX

执行:POP AX

第一步,从 SS:SP 取一个字:

AL ← SS:SPAH ← SS:SP + 1

如果栈顶两个字节是:

34H12H

那么 AX 变成:AX = 1234H

第二步:SP ← SP + 2

栈顶恢复到弹出之前的上方位置。

3. PUSH/POP 核心记忆

PUSH:先 SP - 2,再写入数据POP:先读出数据,再 SP + 2

以及:8086 栈向低地址增长8086 栈操作一次处理 16 位

三、堆栈使用规则 书上总结了 4 条。

1. 遵循后进先出

也就是:Last In First OutLIFO

后压入的数据先弹出。

例如:PUSH AX PUSH BX POP CX POP DX

执行顺序是:

先压 AX再压 BX先弹出 BX 给 CX再弹出 AX 给 DX

所以结果是:

CX = 原 BXDX = 原 AX

2. 操作数必须是字

PUSH/POP 不操作字节。

不能:PUSH AL POP BL因为 AL、BL 是 8 位。

可以:PUSH AX POP BX 因为 AX、BX 是 16 位。

3. PUSH 可以用 CS,POP 不能用 CS

前面说过:PUSH CS

可以用来保存当前代码段。

但是:POP CS不允许。

4. 堆栈操作支持多种寻址方式

书上说,8086/8088 的栈操作可以使用除了立即寻址之外的各种寻址方式。

也就是说:PUSH AX PUSH [BX] PUSH WORD PTR [BP+SI] PUSH DS

这些都可以。但不能:PUSH 1234H

因为 8086 原始指令中 PUSH 立即数不是基本 8086 指令的形式,后来的 80186 以后才支持 PUSH imm。

四、地址传送指令

接下来进入 地址传送指令。这类指令不是传送“内存里面的数据”,而是传送:

地址 偏移量 段地址

书上说有三条:

LEA LDSLES

五、LEA 指令:装入有效地址

LEA 是 Load Effective Address。

格式:LEA reg16, mem16

功能:reg16 ← mem16 的有效地址 EA

注意,LEA 取的是地址,不是地址里面的数据。

1. LEA 和 MOV 的区别

书上给了一个很重要的对比:

MOV DI, TABLE LEA DI, TABLE

这两个不一样。

假设 TABLE 是一个变量名,对应某个内存单元。MOV DI, TABLE

表示:把 TABLE 这个内存单元里的内容送到 DI而:LEA DI, TABLE

表示:把 TABLE 的偏移地址送到 DI

也就是:DI = OFFSET TABLE

所以最关键区别是:MOV 取内容LEA 取地址

2. 举例理解

假设:TABLE 的偏移地址 = 2000HTABLE 里面存的数据 = 1234H

执行:MOV DI, TABLE

结果是:DI = 1234H

执行:LEA DI, TABLE

结果是:DI = 2000H

一个取内容,一个取地址,千万不要混。

3. LEA 的常见用途

LEA 经常用来建立地址指针。例如:LEA SI, ARRAY

意思是:SI = ARRAY 的偏移地址

之后就可以:

MOV AL, [SI] INC SI MOV BL, [SI]

逐个访问数组元素。所以 LEA 特别适合:

数组 表格 字符串 连续变量区

六、LDS 和 LES:装入远地址指针

接下来是:

LDS reg16, mem32 LES reg16, mem32

这两个比 LEA 更复杂一点。

1. LDS 的功能

格式:LDS reg16, mem32

功能是:

reg16 ← mem32 中的低 16 位DS ← mem32 中的高 16 位

也就是说,内存里连续 4 个字节保存了一个远地址:

偏移地址 2 字节段地址 2 字节

LDS 会一次性把:偏移地址送入通用寄存器,段地址送入 DS

2. LES 的功能

格式:LES reg16, mem32

功能类似,只不过高 16 位送入 ES:

reg16 ← mem32 中的低 16 位ES ← mem32 中的高 16 位

3. LDS 例子

书上给:LDS SI, [0100H]

假设当前 DS = 3000H。那么 CPU 先去访问:DS:0100H

物理地址:3000H × 10H + 0100H = 30100H

假设内存内容是:

[30100H] = 80H[30101H] = 20H[30102H] = 00H[30103H] = 25H

由于小端方式:

前两个字节:80H, 20H → 2080H

后两个字节:00H, 25H → 2500H

所以执行后:

SI = 2080HDS = 2500H

也就是说,LDS 从内存中取出了一个完整的远指针:2500H:2080H

4. LEA、LDS、LES 的区别

这三个很容易混,直接对比:

LEA:只取偏移地址 EALDS:取偏移地址 + 段地址,段地址送 DS LES:取偏移地址 + 段地址,段地址送 ES

举例:LEA SI, TABLE

结果:SI = TABLE 的偏移地址

LDS SI, PTR_ADDR

结果:SI = PTR_ADDR 中保存的偏移地址DS = PTR_ADDR 中保存的段地址

LES DI, PTR_ADDR

结果:DI = PTR_ADDR 中保存的偏移地址ES = PTR_ADDR 中保存的段地址

七、标志位传送指令 接下来进入 Flags transfer。

8086 中的标志寄存器 FLAGS,也叫程序状态字 PSW,用来记录 CPU 运算状态。

例如:CF:进位标志 ZF:零标志 SF:符号标志OF:溢出标志 PF:奇偶标志 AF:辅助进位标志

有些指令会改变这些标志位,有些指令可以读取或保存这些标志位。

这一类指令包括:LAHF SAHF PUSHF POPF

八、LAHF:把 FLAGS 低 8 位送入 AH

LAHF 是:Load AH from Flags

格式:LAHF

功能:AH ← FLAGS 的低 8 位

具体是把这些标志位装入 AH 的对应位:

AH bit7 ← SFAH bit6 ← ZFAH bit4 ← AFAH bit2 ← PFAH bit0 ← CF

AH 的第 5、3、1 位不是重点,可以暂时不管。

所以 LAHF 可以理解成:把一部分 FLAGS 复制到 AH

注意,LAHF 本身不改变 FLAGS。

九、SAHF:把 AH 写回 FLAGS 低 8 位

SAHF 是:Store AH into Flags格式:SAHF

功能:FLAGS 的低 8 位 ← AH

具体是:

SF ← AH bit7ZF ← AH bit6AF ← AH bit4PF ← AH bit2CF ← AH bit0

所以 SAHF 可以用 AH 来修改部分标志位。

1. 例 4.7:修改 SF 为 1

书上例子:

LAHF OR AH, 80H SAHF

我们一步一步看。

第一条:LAHF把 FLAGS 的低 8 位读到 AH。

第二条:OR AH, 80H

80H 的二进制是:1000 0000B

和 AH 做 OR,就会把 AH 的第 7 位置 1。

AH 第 7 位对应 SF。

第三条:SAHF把 AH 写回 FLAGS,于是 SF 被置 1。

所以这一段的功能是:把 SF 标志位置 1

十、PUSHF 和 POPF

PUSHF / POPF 是对整个 FLAGS 寄存器进行堆栈操作。

1. PUSHF

格式:PUSHF功能:SP ← SP - 2SS:SP ← FLAGS

也就是把 FLAGS 整个压入堆栈。常用于保存当前标志状态。

2. POPF

格式:POPF

功能:FLAGS ← SS:SPSP ← SP + 2

也就是从堆栈弹出一个字送回 FLAGS。常用于恢复标志状态。

3. PUSHF/POPF 的常见用途

比如进入子程序前,希望保存当前 FLAGS,出来后恢复:PUSHF中间执行一些会改变标志位的指令POPF

执行后 FLAGS 恢复到 PUSHF 之前的状态。

4. 用 PUSHF/POPF 修改 TF

书上例 4.8 是修改 TF 标志位。

代码大意:

PUSHF POP AX OR AH, 01H PUSH AX POPF

这里的思想是:

PUSHF 把 FLAGS 压栈。

POP AX 把 FLAGS 弹到 AX 里。

修改 AX 中对应 TF 的那一位。

PUSH AX 把修改后的值压回栈。

POPF 把它送回 FLAGS。

TF 是单步标志位,置 1 后 CPU 执行一条指令就会产生单步中断,调试程序时用。

十一、IN / OUT 输入输出指令

接下来是 I/O 数据传送。

8086 用专门指令访问 I/O 端口:

INOUT

它们都隐含使用累加器:8 位数据用 AL 16 位数据用 AX

1. IN 指令

格式:

IN AL, port IN AX, port

功能:AL 或 AX ← I/O 端口数据

例如:IN AL, 70H 表示从端口 70H 读一个字节送入 AL。

2. OUT 指令

格式:

OUT port, AL OUT port, AX

功能:I/O 端口 ← AL 或 AX

例如:OUT 70H, AL 表示把 AL 的内容输出到端口 70H。

3. 用 DX 指定端口

当端口地址超过 8 位时,要用 DX。

书上例子:

MOV DX, 3F3H IN AX, DX

表示:从端口 3F3H 输入一个 16 位数据到 AX

如果是 16 位输入:

AL ← 端口 3F3HAH ← 端口 3F4H

因为 AX 是两个字节。

输出类似:OUT DX, AX

表示:

端口 3F3H ← AL端口 3F4H ← AH

目前数据传送类指令已经讲了:

MOV:普通传送XCHG:交换XLAT:查表转换PUSH / POP:堆栈传送LEA:取有效地址LDS / LES:取远地址指针LAHF / SAHF:FLAGS 低 8 位和 AH 之间传送PUSHF / POPF:FLAGS 和堆栈之间传送IN / OUT:端口输入输出

这一组指令的共同特点是:

主要负责搬数据、搬地址、搬标志位一般不做真正的算术运算