汇编与调试第6周复习笔记(含老师样例代码)
1. 本周学习目标
- 掌握按字节批量写内存的基本循环写法
- 理解跨 64K 边界时,如何通过调整段寄存器继续访问后续内存
- 理解
seg与offset的作用 - 掌握变量/数组在汇编里的多种等价访问形式
2. 知识点整理
2.1 批量填充内存(按字节)
核心操作是:
- 先设定段寄存器(如
ds=8000h) - 用
bx作为偏移地址 - 用循环控制写入次数
- 每次执行
mov ds:[bx], al后bx++
2.2 为什么 cx 置 0 仍可循环 65536 次
在 16 位寄存器里:
cx=0- 每次
sub cx,1 - 首次减法后变成
FFFFh - 直到再次减到
0000h时ZF=1才退出
因此这个技巧可用于“刚好一整段 64K 字节”的循环。
2.3 跨段写内存的两种方法
当目标长度超过 64K 时,需要跨段:
- 分段块写法(外循环按段)
- 每写完一段,
ds += 1000h - 再继续从偏移
0000h写
- 单循环边界检测法
- 用大计数器(如
ecx=20000h)统一控制总次数 - 每次写完后检查
bx是否到FFFFh - 若到边界,更新
ds += 1000h
2.4 byte ptr 何时必需
mov ds:[bx], al:目的宽度可由al推断(8位),可省略byte ptrmov ds:[bx], 0FFh:立即数本身宽度不明确,通常要写byte ptr
2.5 变量与数组寻址
常见伪指令:
seg abc:取变量abc所在段地址offset abc:取变量abc的偏移地址
常见等价访问:
mov dl, [abc]mov dl, abcmov dl, ds:[abc]mov dl, ds:abc[0]
它们本质都在表达“读取 abc 首字节”。
2.6 assume 的编译期作用
assume ds:data 不是运行时赋值指令,它是给编译器的“映射线索”:
- 帮助编译器把
data:这种逻辑段名映射到具体段寄存器(如ds) - 实际运行时仍要靠
mov ds, ax真正完成段寄存器初始化
3. 老师样例代码(第6周)
3.1 1.asm(写满 8000h:0000h 起 10000h 字节)
asm
;把8000h:0000h起,长度为10000h的内存块全部填成0FFh
code segment
assume cs:code
main:
mov ax, 8000h
mov ds, ax
mov bx, 0 ; ds:bx-->目标内存块
mov cx, 0 ; cx控制循环次数
mov al, 0FFh
again:
mov ds:[bx], al ; 或写成 mov byte ptr ds:[bx], al
; 或写成 mov byte ptr ds:[bx], 0FFh
; 上面这行指令中的byte ptr不可省略,
; 因为汇编语言中的常数并没有明确的宽度
add bx, 1
sub cx, 1
jnz again
mov ah, 4Ch
mov al, 0
int 21h
code ends
end main3.2 2.asm(写满 20000h 字节,分段外循环)
asm
;把8000h:0000h起,长度为20000h的内存块全部填成0FFh
code segment
assume cs:code
main:
mov ax, 8000h
mov ds, ax
mov dx, 2 ; 控制外循环的次数
mov bx, 0 ; ds:bx-->目标内存块
next_segment:
mov cx, 0 ; cx控制循环次数
mov al, 0FFh
again:
mov ds:[bx], al ; 或写成 mov byte ptr ds:[bx], al
; 或写成 mov byte ptr ds:[bx], 0FFh
; 上面这行指令中的byte ptr不可省略,
; 因为汇编语言中的常数并没有明确的宽度
add bx, 1
sub cx, 1
jnz again
mov ax, ds
add ax, 1000h
mov ds, ax
sub dx, 1
jnz next_segment
mov ah, 4Ch
mov al, 0
int 21h
code ends
end main3.3 3.asm(写满 20000h 字节,单循环 + 边界检测)
asm
;把8000h:0000h起,长度为20000h的内存块全部填成0FFh
.386
code segment use16
assume cs:code
main:
mov ax, 8000h
mov ds, ax
mov dx, 2 ; 控制外循环的次数
mov bx, 0 ; ds:bx-->目标内存块
mov ecx, 20000h ; ecx控制循环次数
mov al, 0FFh
again:
mov ds:[bx], al ; 或写成 mov byte ptr ds:[bx], al
; 或写成 mov byte ptr ds:[bx], 0FFh
; 上面这行指令中的byte ptr不可省略,
; 因为汇编语言中的常数并没有明确的宽度
cmp bx, 0FFFFh
jne next
mov dx, ds
add dx, 1000h
mov ds, dx
next:
add bx, 1
sub ecx, 1
jnz again
mov ah, 4Ch
mov al, 0
int 21h
code ends
end main3.4 4.asm(自定义变量与数组访问)
asm
;如何访问自定义变量和数组
data segment
xyz db 1,2,3
abc db "Hello", 0 ; db:define byte, 相当于C语言的char类型
;char abc[] = "Hello";
data ends
code segment
assume cs:code, ds:data
var db "asm"
main:
mov ax, seg abc ; 在源代码中,用seg abc可以获得abc的段地址
; 也可以用abc所在段的段名data来表示abc的
; 段地址, 即本语句也可以写成:
; mov ax, data
mov ds, ax
mov bx, offset abc ; offset abc表示abc的偏移地址
again:
mov dl, ds:[bx] ; 或写成mov dl, byte ptr ds:[bx]
cmp dl, 0
je done
mov ah, 2
int 21h
add bx, 1
jmp again
done: ;
;mov dl, ds:[abc] ; [abc]中的abc就是offset abc
; mov dl, ds:[abc] 等价于 mov dl, ds:abc[0], 其中abc[0]等价于[abc+0]
; mov dl, ds:[abc] 也等价于 mov dl, ds:abc
; 这3种形式均会编译成: mov dl, ds:[3]
mov dl, [abc] ; 这第4种形式跟上述3种形式也是等价的
; 编译后仍旧会变成: mov dl, ds:[3]
; 编译过程如下:
; ① mov dl, data:[3]
; 汇编语言指令在引用变量时,该变量的段地址不能
; 用常数表示,只能用某个段寄存器表示,故
; 编译器需要根据assume语句提供的线索把
; data:替换成ds:
; ② mov dl, ds:[3]
; assume ds:data的作用是告诉编译器把
; data:替换成ds:
; mov dl, [abc] 也可以写成 mov dl, abc
; 后者在编译时会先转化成前者再编译
mov ah, 2
int 21h
mov dl, [var] ; 编译过程如下:
; ① mov dl, code:[0]
; ② mov dl, cs:[0]
mov ah, 2
int 21h
mov ah, 4Ch
mov al, 0
int 21h
code ends
end main4. 本周易错点清单
- 误把
assume当成运行时赋值指令 - 忘记先初始化
ds就直接访问数据段变量 - 立即数写内存时不写宽度修饰(
byte ptr) - 跨 64K 连续写内存时忘了更新
ds bx到边界时处理顺序写错,导致跳段偏移异常- 混淆
seg(段地址)与offset(偏移地址)
5. 复习建议
- 先对比 1.asm 与 2.asm,理解“单段循环”和“跨段外循环”的差别。
- 再对比 2.asm 与 3.asm,理解“分段处理”与“单循环边界检测”两种写法。
- 最后通读 4.asm,把
seg/offset/[abc]/abc的等价关系逐条默写一遍。 - 自己加练:把填充值从
0FFh改成055h,并改为每隔 1 字节写一次(步长变 2)。
6. 一页速记
- 整段 64K 填充:
cx=0+sub cx,1+jnz - 跨段推进:
ds = ds + 1000h seg var取段地址,offset var取偏移地址assume只影响编译器,不会改变寄存器- 变量访问可写
var、[var]、ds:[var](在语义一致前提下)