内存寻址与数据处理

汇编指令处理的数据可以来自三个地方: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字节),因为栈操作以字为单位
  • divmul:长度由除数/乘数决定(见下文)

div 指令(除法)

div 是除法指令,规则比较特殊:

除数位数被除数位置余数
8位(reg/内存字节)AX(16位)ALAH
16位(reg/内存字)DX(高16位) + AX(低16位)(共32位)AXDX
; 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位置零。

数据传送的限制

汇编的数据移动不像高级语言那么自由,有一些硬性限制:

  1. 不能直接从内存到内存mov [0], [1] ❌,必须经过寄存器中转
  2. 不能直接给段寄存器赋值mov ds, 1000H ❌(详见 01-寄存器
  3. 不能直接内存到内存移动:要借助寄存器或栈
; 想把 ds:0 的字复制到 ds:2
mov ax, [0]
mov [2], ax         ; ✅ 通过 AX 中转

相关