第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 ptr1.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 main1.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 ends2.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 main2.5 assume 的真正作用
assume ds:data 不是运行时赋值指令!它只是告诉编译器:
- 遇到
data:这种逻辑段名时,把它替换成对应的段寄存器(如ds:) - 实际的段寄存器初始化仍然要靠
mov ds, ax来完成
常见错误:写了 assume 但不写
mov ds, ax,以为 ds 已经自动初始化了。
三、本周易错点
- 误把
assume当成运行时赋值,忘记mov ds, ax初始化 - 立即数写内存时不写宽度修饰(
byte ptr) - 跨 64K 连续写内存时忘了更新 ds
- 混淆
seg(段地址)和offset(偏移地址) - 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 |