Skip to content

汇编与调试第6周复习笔记(含老师样例代码)

1. 本周学习目标

  • 掌握按字节批量写内存的基本循环写法
  • 理解跨 64K 边界时,如何通过调整段寄存器继续访问后续内存
  • 理解 segoffset 的作用
  • 掌握变量/数组在汇编里的多种等价访问形式

2. 知识点整理

2.1 批量填充内存(按字节)

核心操作是:

  • 先设定段寄存器(如 ds=8000h
  • bx 作为偏移地址
  • 用循环控制写入次数
  • 每次执行 mov ds:[bx], albx++

2.2 为什么 cx 置 0 仍可循环 65536 次

在 16 位寄存器里:

  • cx=0
  • 每次 sub cx,1
  • 首次减法后变成 FFFFh
  • 直到再次减到 0000hZF=1 才退出

因此这个技巧可用于“刚好一整段 64K 字节”的循环。

2.3 跨段写内存的两种方法

当目标长度超过 64K 时,需要跨段:

  1. 分段块写法(外循环按段)
  • 每写完一段,ds += 1000h
  • 再继续从偏移 0000h
  1. 单循环边界检测法
  • 用大计数器(如 ecx=20000h)统一控制总次数
  • 每次写完后检查 bx 是否到 FFFFh
  • 若到边界,更新 ds += 1000h

2.4 byte ptr 何时必需

  • mov ds:[bx], al:目的宽度可由 al 推断(8位),可省略 byte ptr
  • mov ds:[bx], 0FFh:立即数本身宽度不明确,通常要写 byte ptr

2.5 变量与数组寻址

常见伪指令:

  • seg abc:取变量 abc 所在段地址
  • offset abc:取变量 abc 的偏移地址

常见等价访问:

  • mov dl, [abc]
  • mov dl, abc
  • mov 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 main

3.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 main

3.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 main

3.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 main

4. 本周易错点清单

  • 误把 assume 当成运行时赋值指令
  • 忘记先初始化 ds 就直接访问数据段变量
  • 立即数写内存时不写宽度修饰(byte ptr
  • 跨 64K 连续写内存时忘了更新 ds
  • bx 到边界时处理顺序写错,导致跳段偏移异常
  • 混淆 seg(段地址)与 offset(偏移地址)

5. 复习建议

  1. 先对比 1.asm 与 2.asm,理解“单段循环”和“跨段外循环”的差别。
  2. 再对比 2.asm 与 3.asm,理解“分段处理”与“单循环边界检测”两种写法。
  3. 最后通读 4.asm,把 seg/offset/[abc]/abc 的等价关系逐条默写一遍。
  4. 自己加练:把填充值从 0FFh 改成 055h,并改为每隔 1 字节写一次(步长变 2)。

6. 一页速记

  • 整段 64K 填充:cx=0 + sub cx,1 + jnz
  • 跨段推进:ds = ds + 1000h
  • seg var 取段地址,offset var 取偏移地址
  • assume 只影响编译器,不会改变寄存器
  • 变量访问可写 var[var]ds:[var](在语义一致前提下)