← 返回 计组与微机控制

计组与微机控制

7.2 转移寻址

7.2 转移寻址

一、寄存器间接相对寻址

寄存器间接相对寻址。这个名字看起来很长,其实就是:[寄存器 + 位移量]

也就是有效地址 EA 等于:EA = 某个寄存器的内容 + 指令中给出的位移量

这里能用的寄存器包括:BX、BP、SI、DI

例如:MOV [SI + 20H], AX

意思是:把 AX 的内容送到内存单元 DS:[SI + 20H]

如果:

DS = 2000HSI = 1000Hdisp = 20H

那么先算有效地址:

EA = SI + disp = 1000H + 20H = 1020H

再算物理地址:

物理地址 = DS × 10H + EA = 2000H × 10H + 1020H = 21020H

所以 MOV [SI + 20H], AX 就是把 AX 写到物理地址 21020H 开始的两个字节中。

1. 为什么叫“相对”?

因为它不是只用寄存器内容作为地址,而是在寄存器内容的基础上再加一个位移量。

比如:[SI]

是寄存器间接寻址。而:[SI + 20H]

是在 SI 的基础上偏移 20H,所以叫相对寻址。

你可以理解成:SI 指向一个表的起点 ,20H 表示表内某个元素的位置

所以这种方式很适合访问数组、表格、结构体中的某个字段。

2. 默认段寄存器仍然要注意

只要用内存寻址,就要考虑默认段。

规则还是:BX、SI、DI 默认用 DS,BP 默认用 SS

所以:MOV AX, [SI + 20H]

默认是:MOV AX, DS:[SI + 20H]

而:MOV AX, [BP + 20H]

默认是:MOV AX, SS:[BP + 20H]

如果想改默认段,可以用段超越前缀,比如:

MOV AX, ES:[SI + 20H]

意思就是强制从 ES 段取数。

二、基址变址相对寻址

基址变址相对寻址。这个名字更长,但公式很简单:

EA = 基址寄存器 + 变址寄存器 + 位移量

也就是:

[BX + SI + disp] [BX + DI + disp][BP + SI + disp] [BP + DI + disp]

其中:基址寄存器:BX 或 BP变址寄存器:SI 或 DI

1. 举例

例如:MOV [BX + SI + 20H], AX

表示:EA = BX + SI + 20H

如果:

DS = 3000H BX = 2000HSI = 1000H disp = 20H

那么:EA = 2000H + 1000H + 20H = 3020H

物理地址 = DS × 10H + EA = 3000H × 10H + 3020H = 33020H

所以 AX 会被写入从物理地址 33020H 开始的两个字节。

2. 为什么这种方式适合数组和表格?

你可以这样想:

BX:表的首地址SI:当前元素的下标偏移disp:结构体字段偏移

比如一个学生表,每个学生记录有姓名、学号、成绩等字段。

如果 BX 指向学生表开头,SI 指向第几个学生,disp 表示“成绩字段”在记录中的偏移量,那么:[BX + SI + disp]

就可以访问某个学生的某个字段。所以基址变址相对寻址非常适合:

数组 二维数组 表格结构体 堆栈中的局部变量

3. 课本给的几种写法本质一样

课本列了几种形式:

MOV [BX + SI + disp], AX MOV disp[BX + SI], AX MOV [BX + SI]disp, AX MOV [BX]disp[SI], AX

它们本质都是:EA = BX + SI + disp只是汇编书写形式不同。

现代写法更常见的是:MOV [BX + SI + disp], AX

三、寻址方式阶段总结

到这里,8086 常见的数据存储器寻址方式可以总结为:

直接寻址:EA = disp寄存器间接寻址:EA = BX / BP / SI / DI寄存器相对寻址:EA = BX / BP / SI / DI + disp基址变址寻址:EA = BX/BP + SI/DI基址变址相对寻址:EA = BX/BP + SI/DI + disp

其中默认段寄存器:只要寻址中出现 BP,默认 SS 否则通常默认 DS

四、I/O 端口寻址方式

CPU 和外部设备之间通信,通常不是直接访问设备本身,而是访问 I/O 接口电路中的端口。

比如键盘、显示器、串口、并口、定时器这些设备,都可以通过端口和 CPU 交换数据。

1. 什么是端口?

端口可以理解成 I/O 接口里的一个寄存器。

CPU 访问端口,就像访问某个特殊地址一样。

例如:端口 70H 端口 3F8H 端口 60H

每个端口都有一个端口地址。8086/8088 专门用:

INOUT

指令访问 I/O 端口。

五、I/O 端口直接寻址

第一种是:直接端口寻址。

格式大概是:

IN AL, port OUT port, AL

这里的 port 是一个 8 位立即数。所以直接端口寻址的端口范围是:00H ~ FFH

也就是只能直接访问 256 个端口。例如:IN AL, 70H

意思是:从 I/O 端口地址 70H 读取一个字节,送入 AL

如果是输出:

OUT 70H, AL

意思是:把 AL 的内容输出到 I/O 端口 70H

1. 为什么一般用 AL 或 AX?

8086 的 IN/OUT 指令中,数据寄存器通常固定使用累加器:

8 位端口数据:AL16 位端口数据:AX

所以常见形式是:

IN AL, 端口IN AX, 端口OUT 端口, AL OUT 端口, AX

不能随便写成:IN BL, 70H

这种一般不合法,因为 IN 指令隐含使用 AL/AX。

六、I/O 端口间接寻址 第二种是:间接端口寻址。

当端口地址超过 8 位,比如:350H 3F8H 03FCH

就不能直接写成 8 位立即数了。

这时要先把 16 位端口地址放到 DX 中。

例如课本给:MOV DX, 350H OUT DX, AL

意思是:先把端口地址 350H 送入 DX再把 AL 的内容输出到 DX 所指定的端口

所以:OUT DX, AL

不是把 AL 输出到 DX 寄存器,而是输出到:

端口地址 = DX 的内容

也就是:端口 350H

1. 直接端口和间接端口对比

直接端口寻址:端口地址直接写在指令中 范围 00H~FFH间接端口寻址:端口地址放在 DX 中 范围 0000H~FFFFH

所以可以这样记:小端口地址直接写 大端口地址放 DX

七、隐含寻址

有些指令表面上没有把全部操作数写出来,但 CPU 内部规定了默认使用哪些寄存器。这就是隐含寻址。

1. MUL 指令例子

课本举:MUL reg8

如果是 8 位乘法,它实际含义是:AX = AL × reg8

也就是说,指令中只写了一个操作数 reg8,但另一个操作数 AL 是隐含的,结果 AX 也是隐含的。

例如:MUL BL

真正意思是:AX = AL × BL

没有写 AL,也没有写 AX,但 CPU 默认就是这么用。

2. 为什么要有隐含寻址?

因为有些指令历史上就规定好了固定寄存器。这样可以让机器码更短。

比如乘法如果每次都要写:MUL AX, AL, BL

机器码就会更复杂。所以 8086 规定:

8 位乘法默认用 AL,结果放 AX16 位乘法默认用 AX,结果放 DX:AX

这就是隐含寻址。

八、转移地址寻址方式

接下来进入一个新大类:转移地址的寻址方法。

前面讲的数据寻址,是为了找到数据在哪里。

现在讲转移地址寻址,是为了找到:程序下一步跳到哪里执行

涉及的指令主要有:

JMP CALL LOOP INT

程序正常执行时,CPU 根据:CS:IP

取下一条指令。如果不跳转,IP 自动增加,程序顺序执行。

如果要改变执行顺序,就要修改:IP

或者同时修改:CS 和 IP

九、段内转移和段间转移

程序转移分两种情况。

1. 段内转移

目标地址还在当前代码段 CS 内。

也就是说:CS 不变 只修改 IP

例如:JMP L1

如果 L1 还在当前代码段,那么只需要改 IP。

这种叫:段内转移

也叫 near 转移。

2. 段间转移

目标地址在另一个代码段。这时不仅 IP 要变,CS 也要变。

也就是:CS 改成新段地址IP 改成新偏移地址

这种叫:段间转移 也叫 far 转移。

十、段内直接寻址

段内直接寻址。这种方式中,指令里给出的不是目标地址本身,而是相对于当前 IP 的位移量 disp。

公式是:目标 IP = 当前 IP + disp

注意,这里的“当前 IP”通常指取完这条转移指令后的 IP,也就是下一条指令的偏移地址。

1. 为什么不是直接存目标地址?

比如:JMP L1

机器码里不一定直接存 L1 的绝对偏移地址,而是存:

从当前 IP 到 L1 的距离这叫相对转移。

优点是程序整体搬到别的位置时,只要相对距离不变,跳转仍然有效。

2. 图 4.8 怎么看?

7.2 转移寻址 图 1

图中例子类似:JMP L1

机器码是:E9 34 12H

其中:E9:JMP near 的操作码34 12:位移量 1234H,小端存放

假设:CS = 2000H原 IP = 1000H这条 JMP 指令长度 = 3 字节

取完这条指令后,IP 先变成:

当前 IP = 1003H然后加位移量:

目标 IP = 1003H + 1234H = 2237H

物理地址:

CS × 10H + 目标 IP= 2000H × 10H + 2237H= 22237H

所以 CPU 跳到当前代码段内偏移 2237H 的地方继续执行。

十一、段内间接寻址

段内间接寻址,就是目标偏移地址不直接写在指令里,而是放在寄存器或内存中。

但是仍然只修改 IP,不修改 CS。

1. 寄存器间接:JMP BX

例如:JMP BX意思是:IP = BX

CS 不变

如果:CS = 2000HBX = 1500H

那么跳转目标是:CS:IP = 2000H:1500H

物理地址:2000H × 10H + 1500H = 21500H

所以 JMP BX 是跳到当前代码段内,偏移地址由 BX 指定的位置。

2. 存储器间接:JMP WORD PTR [BX]

例如:JMP WORD PTR [BX]

意思不是跳到 [BX] 这个地址本身,而是:

从 DS:[BX] 开始取一个字,把这个字作为新的 IPCS 不变

假设:DS = 3000HBX = 1234H

那么先访问内存地址:物理地址 = DS × 10H + BX = 3000H × 10H + 1234H = 31234H

如果内存中:

[31234H] = 26H[31235H] = 31H

由于小端存放,取出的字是:3126H

所以:IP = 3126H

CS 不变

CPU 跳到当前 CS 段内偏移 3126H 的位置继续执行。

十二、段间直接寻址

段间直接寻址要求指令中直接给出目标地址的完整形式:

目标 IP目标 CS

也就是 32 位信息:16 位偏移地址 + 16 位段地址

例如:JMP FAR PTR P1

意思是跳到另一个代码段的 P1 处。

执行时:

IP ← 指令中给出的偏移地址CS ← 指令中给出的段地址

因为 CS 也变了,所以这是远转移。

1. 机器码里的顺序

8086 小端存放,所以段间直接转移的目标地址在机器码中通常是:

先放偏移地址低字节再放偏移地址高字节再放段地址低字节再放段地址高字节

也就是:IP 在前,CS 在后这点要注意。

十三、段间间接寻址

段间间接寻址和段内间接类似,只不过这次内存中存的不是一个 16 位 IP,而是完整的:IP + CS也就是 4 个字节。

例如:JMP DWORD PTR [BX + 020AH]

意思是:从 DS:[BX + 020AH] 开始取 4 个字节前两个字节作为 IP后两个字节作为 CS然后跳转到新的 CS:IP

1. 图 4.12 怎么看?

图中例子大概是:

7.2 转移寻址 图 2

DS = 2000HBX = 1400H位移量 = 020AH

先算保存目标地址的内存位置:

EA = BX + 020AH = 1400H + 020AH = 160AH

物理地址:

DS × 10H + EA= 2000H × 10H + 160AH= 2160AH

然后从 2160AH 开始取 4 个字节。

假设内存中存的是:

2160AH: 0EH2160BH: 32H2160CH: 00H2160DH: 40H

小端解释:

IP = 320EHCS = 4000H

最后 CPU 跳转到:4000H:320EH

对应物理地址:4000H × 10H + 320EH = 4320EH

所以段间间接寻址的本质是:内存中存着一个远地址指针CPU 从内存取出这个远地址,再跳过去

十四、转移寻址总结

这四种可以这样记:

段内直接:目标 = 当前 IP + 位移量只改 IP段内间接:目标 IP 在寄存器或内存中只改 IP段间直接:目标 CS:IP 直接在指令中同时改 CS 和 IP段间间接:目标 CS:IP 存在内存中从内存取出后,同时改 CS 和 IP

判断关键是两个问题:1. 改不改 CS?不改就是段内,改就是段间。2. 目标地址是直接在指令里,还是在寄存器/内存里?直接给就是直接寻址,通过寄存器或内存取就是间接寻址。

十五、4.3:8086/8088 指令系统分类

课本把 8086/8088 指令分为六类:

1. 数据传送类指令2. 算术运算类指令3. 位操作类指令4. 串操作类指令5. 控制转移类指令6. 处理器控制类指令

这个分类很重要,后面会一类一类讲。

数据传送:搬东西算术运算:加减乘除位操作:逻辑运算、移位串操作:批量处理内存数据控制转移:跳转、调用、返回、中断处理器控制:控制标志位、中断允许、CPU 状态