Skip to content

第6讲:内存操作、段地址与变量访问

本周核心:理解内存批量填充的写法,掌握跨 64K 边界的方法,理解 seg/offset 和变量寻址。


一、批量填充内存

1.1 单段填满 64KB

目标:把 8000h:0000h 起、长度为 10000h(64KB)的内存全部填成 0FFh

asm
; 把 8000h:0000h 起,长度为 10000h 字节的内存全部填成 0FFh
code segment
assume cs:code
main:
   mov ax, 8000h
   mov ds, ax       ; ds = 段地址 8000h
   mov bx, 0        ; bx = 偏移地址(从 0 开始)
   mov cx, 0        ; cx 控制循环次数
   mov al, 0FFh
again:
   mov ds:[bx], al  ; 写一个字节到目标地址
   add bx, 1        ; 偏移地址 +1
   sub cx, 1
   jnz again        ; cx ≠ 0 就继续
   mov ah, 4Ch
   mov al, 0
   int 21h
code ends
end main

为什么 cx = 0 但能循环 65536 次?

在 16 位寄存器中,cx = 0 第一次执行 sub cx, 1 后变成 FFFFh(65535),之后继续递减直到再次变成 0 时 ZF=1 才退出循环。恰好是 65536 次——也就是一整段 64KB。

什么时候必须写 byte ptr?

asm
mov ds:[bx], al          ; al 是 8 位寄存器,编译器可推断宽度,可省略 byte ptr
mov byte ptr ds:[bx], 0FFh  ; 常数 0FFh 宽度不明确,必须加 byte ptr

1.2 跨 64K 段写 128KB 内存(分段外循环法)

当目标长度超过 64KB 时,需要跨段。方法:每写完一段(64KB),ds += 1000h,再写下一段。

段地址加 1000h = 物理地址加 10000h = 正好 64KB,因为段地址的低 4 位(乘以 16 后变成物理地址低 4 位)和偏移地址组成完整物理地址。

asm
; 把 8000h:0000h 起,长度为 20000h(128KB)的内存填成 0FFh
code segment
assume cs:code
main:
   mov ax, 8000h
   mov ds, ax
   mov dx, 2        ; 外循环次数 = 2 段
   mov bx, 0
next_segment:   
   mov cx, 0        ; 每段 64KB(65536 次)
   mov al, 0FFh
again:
   mov ds:[bx], al
   add bx, 1
   sub cx, 1
   jnz again
   mov ax, ds       ; 当前段地址
   add ax, 1000h    ; 前进 64KB 物理地址 = 段地址 +1000h
   mov ds, ax
   sub dx, 1
   jnz next_segment
   mov ah, 4Ch
   mov al, 0
   int 21h
code ends
end main

1.3 单循环 + 边界检测法

不用外循环,用一个大计数器(ecx=20000h)统一控制,每步检测 bx 是否到达 FFFFh,到达就更新 ds:

asm
.386
code segment use16
assume cs:code
main:
   mov ax, 8000h
   mov ds, ax
   mov bx, 0
   mov ecx, 20000h  ; 总字节数
   mov al, 0FFh   
again:  
   mov ds:[bx], al
   cmp bx, 0FFFFh
   jne next
   mov dx, ds       ; bx 到达段边界
   add dx, 1000h    ; 段地址 +1000h
   mov ds, dx
next:   
   add bx, 1
   sub ecx, 1
   jnz again
   mov ah, 4Ch
   mov al, 0
   int 21h
code ends
end main

二、变量与数组访问

2.1 数据段定义

asm
data segment
xyz db 1, 2, 3             ; db: define byte, 相当于 C 的 char
abc db "Hello", 0          ; 字符串,0 结尾(类似 C 风格)
data ends

2.2 seg 与 offset

asm
mov ax, seg abc     ; 取 abc 的段地址(等价于 mov ax, data)
mov bx, offset abc  ; 取 abc 的偏移地址

seg 取段地址,offset 取偏移地址。seg abc 本质就是 abc 所在段 data 的段地址。

2.3 变量的多种等价写法

以下四种写法完全等价,都读取 abc 的首字节:

asm
mov dl, ds:[abc]       ; 最完整的写法
mov dl, ds:abc[0]      ; abc[0] 等价于 [abc+0]
mov dl, [abc]          ; 省略 ds:(assume ds:data 让编译器自动补上)
mov dl, abc            ; 省略方括号和 ds:

编译过程示例:

mov dl, [abc]
→ ① mov dl, data:[3]       (编译器根据 assume ds:data 把 abc 转换成段:偏移)
→ ② mov dl, ds:[3]         (把 data: 替换成 ds:)

2.4 遍历字符串(类似 C 的指针遍历)

asm
data segment
abc db "Hello", 0
data ends

code segment
assume cs:code, ds:data
var db "asm"          ; var 在代码段中
main:
   mov ax, data
   mov ds, ax
   mov bx, offset abc ; bx = 字符串首地址(类似 char *p = abc)
again:
   mov dl, ds:[bx]    ; 取当前字符
   cmp dl, 0
   je done            ; 遇到 0 结束
   mov ah, 2
   int 21h            ; 输出字符
   add bx, 1          ; 指针后移
   jmp again
done:
   ; ds:[abc] 和 [abc] 和 abc 都等价
   mov dl, [abc]      ; 读取 abc 首字节 'H'
   mov ah, 2
   int 21h
   mov dl, [var]      ; var 在 code 段,编译过程:
                      ; ① mov dl, code:[0]
                      ; ② mov dl, cs:[0]
   mov ah, 2
   int 21h
   mov ah, 4Ch
   mov al, 0
   int 21h
code ends
end main

2.5 assume 的真正作用

assume ds:data 不是运行时赋值指令!它只是告诉编译器:

  • 遇到 data: 这种逻辑段名时,把它替换成对应的段寄存器(如 ds:
  • 实际的段寄存器初始化仍然要靠 mov ds, ax 来完成

常见错误:写了 assume 但不写 mov ds, ax,以为 ds 已经自动初始化了。


三、本周易错点

  1. 误把 assume 当成运行时赋值,忘记 mov ds, ax 初始化
  2. 立即数写内存时不写宽度修饰(byte ptr
  3. 跨 64K 连续写内存时忘了更新 ds
  4. 混淆 seg(段地址)和 offset(偏移地址)
  5. cx=0 的 65536 次循环是刻意设计,不要以为程序卡死了

四、速查表

操作写法
整段 64K 填充cx=0 + sub cx,1 + jnz
跨段推进ds = ds + 1000h
取变量段地址mov ax, seg var 或 mov ax, data
取变量偏移地址mov bx, offset var
常数写内存必须加 byte ptr / word ptr / dword ptr