内存寻址与数据处理
汇编指令处理的数据可以来自三个地方:CPU 内部(寄存器/立即数)、内存、端口。这一篇讲数据在哪里、怎么找到它、操作多长。
前置知识
本文涉及寄存器的使用,如果还没看 01-寄存器,建议先阅读。
数据可以在哪里?
机器指令处理的数据有三个来源:
| 位置 | 说明 | 示例 |
|---|---|---|
| 立即数 (idata) | 直接写在指令里,执行前在 CPU 指令缓冲器中 | mov ax, 1 中的 1 |
| 寄存器 | 数据在寄存器中 | mov ax, bx 中的 bx |
| 内存 | 数据在内存单元中,用 [地址] 表示 | mov ax, [0]、mov ax, [bx] |
寻址方式
在内存中找数据,需要给出段地址和偏移地址。
段地址的默认规则
| 偏移寻址方式 | 默认段寄存器 |
|---|---|
[idata] 立即数偏移 | DS |
[bx] | DS |
[si] | DS |
[di] | DS |
[bx+si]、[bx+di] | DS |
[bp] | SS(注意!不是 DS) |
[bp+si]、[bp+di] | SS |
易错点
使用
[bp]时默认段寄存器是 SS(栈段),不是 DS!这是唯一的例外。
显式指定段地址(段超越前缀)
可以在 [] 前加上段前缀,显式指定使用哪个段寄存器:
mov ax, ds:[bp] ; 不用默认的 SS,改用 DS
mov ax, es:[bx] ; 用 ES 段
mov ax, cs:[bx+si] ; 甚至可以用 CS 读代码段中的数据
ds:、es:、ss:、cs: 这些称为段超越前缀。
完整的寻址方式
; 1. 直接寻址
mov ax, [0] ; 偏移地址 = 0(立即数)
; 2. 寄存器间接寻址
mov ax, [bx] ; 偏移地址 = BX 的值
; 3. 寄存器相对寻址
mov ax, [bx+8] ; 偏移地址 = BX + 8
; 4. 基址变址寻址
mov ax, [bx+si] ; 偏移地址 = BX + SI
; 5. 相对基址变址寻址
mov ax, [bx+si+8] ; 偏移地址 = BX + SI + 8
数据长度:操作的是字节还是字?
x86 汇编中一个常见困惑:mov [bx], 0 到底是写一个字节还是一个字?CPU 需要知道操作的数据长度。
方法1:通过寄存器名指明
这是最常见的方式:
; 字操作(16位),因为 AX 是 16 位寄存器
mov ax, 1
mov bx, ds:[0]
mov ds:[0], ax
add ax, 1000
; 字节操作(8位),因为 AL 是 8 位寄存器
mov al, 1
mov al, ds:[0]
mov ds:[0], al
add al, 100
方法2:用 word ptr / byte ptr 显式指明
当指令中没有寄存器名可以推断长度时(比如两边都是内存或立即数),必须显式指定:
; 字操作(2字节)
mov word ptr ds:[0], 1 ; 向 ds:0 写入字数据 0001H
inc word ptr [bx] ; 将 [bx] 处的字加1
add word ptr [bx], 2
; 字节操作(1字节)
mov byte ptr ds:[0], 1 ; 向 ds:0 写入字节数据 01H
inc byte ptr [bx]
add byte ptr [bx], 2
形象理解
地址只是一个”门牌号”,指令决定了你要”进几间房”。
byte ptr就是进1间房(1字节),word ptr就是连进2间房(2字节,一个字)。
方法3:指令默认长度
有些指令本身就隐含了操作长度,不需要指明:
push/pop:永远是字操作(2字节),因为栈操作以字为单位div、mul:长度由除数/乘数决定(见下文)
div 指令(除法)
div 是除法指令,规则比较特殊:
| 除数位数 | 被除数位置 | 商 | 余数 |
|---|---|---|---|
| 8位(reg/内存字节) | AX(16位) | AL | AH |
| 16位(reg/内存字) | DX(高16位) + AX(低16位)(共32位) | AX | DX |
; 8位除法:1000 / 10
mov ax, 1000
mov bl, 10
div bl ; AL = 100(商),AH = 0(余数)
; 16位除法:100000 / 100
mov dx, 0
mov ax, 100000
mov bx, 100
div bx ; AX = 1000(商),DX = 0(余数)
注意
使用 16 位除法时,一定要记得设置 DX!如果被除数只有 16 位,要先
mov dx, 0将高16位置零。
数据传送的限制
汇编的数据移动不像高级语言那么自由,有一些硬性限制:
- 不能直接从内存到内存:
mov [0], [1]❌,必须经过寄存器中转 - 不能直接给段寄存器赋值:
mov ds, 1000H❌(详见 01-寄存器) - 不能直接内存到内存移动:要借助寄存器或栈
; 想把 ds:0 的字复制到 ds:2
mov ax, [0]
mov [2], ax ; ✅ 通过 AX 中转
相关
- 01-寄存器 — 寄存器的完整介绍
- 03-数据定义与伪指令 — 如何在程序中定义数据
- 06-栈 — push/pop 隐含字操作
- 08-过程调用与算术指令 — mul 乘法指令,和 div 类似的规则