计组与微机控制
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:端口输入输出
这一组指令的共同特点是:
主要负责搬数据、搬地址、搬标志位一般不做真正的算术运算
