Turbo C 中用 inline assembly 复制 VGA 缓冲区为何会锁死?
结论
满足相关条件时,这段 rep movsw 循环的复制方向和计数方式是正确的:DS:SI 是源地址,ES:DI 是目标地址,CX 按 word 计数。SIZE2 = 32000 对应 64000 字节,与 SIZE = 64000 一致。
仅凭这段循环无法确定锁死原因。先确认 SEG_buffer、OFF_buffer、SEG_vga 和 OFF_vga 是否指向预期内存。保存和恢复寄存器无法纠正错误的段地址或偏移。
检查段地址和偏移
rep movsw 从 DS:SI 读取数据,写入 ES:DI,每次处理两个字节。复制 32000 个 word 共处理 64000 字节。请逐项确认:
SEG_buffer:OFF_buffer指向图形缓冲区的起始位置,缓冲区至少有 64000 个可读字节。SEG_vga:OFF_vga指向当前图形模式下正确的显存区域,目标区域至少有 64000 个可写字节。SEG_buffer、OFF_buffer等符号展开后是有效的段值和偏移,而不是变量内容的地址或其他无关值。
如果使用的是 320×200、每像素 8 位的线性 VGA 模式,常见的显存起始地址是 A000:0000。其他模式的显存布局可能不同,需要按实际模式确认,不能只凭“VGA”判断。
还要确认缓冲区可以在同一个段内连续访问。SI 对应的 64000 字节范围不能越过 64 KiB 段边界。若 OFF_buffer + 63999 超出 0xFFFF,偏移回绕后不会自动切换到下一个段。显存目标地址也必须覆盖实际写入范围。
检查复制长度
如果 SIZE 表示字节数,而 movsw 每次复制两个字节,word 数量应为 SIZE / 2。当前数值符合这个关系:
SIZE = 64000;
SIZE2 = 32000;
为避免分别维护这两个常量,可以用字节数计算 word 数,前提是 SIZE 为偶数:
mov cx, SIZE/2
cld
rep movsw
如果实际复制长度为奇数,最后一个字节需要单独处理,不能用向下取整后的 word 数代替完整长度。
按地址关系核对汇编
下面是复制逻辑的核心部分。段值和偏移需要替换为程序中真实有效的地址:
asm {
push ds
push si
push di
push cx
push es
mov ax, SEG_buffer
mov ds, ax
mov si, OFF_buffer
mov ax, SEG_vga
mov es, ax
mov di, OFF_vga
mov cx, 32000
cld
rep movsw
pop es
pop cx
pop di
pop si
pop ds
}
这段代码从 DS:SI 复制到 ES:DI。给出的 push/pop 顺序可以对应恢复保存的寄存器,看不出明显的配对错误。cld 会让 movsw 每次递增 SI 和 DI,使复制向高地址进行。
调试建议
先确认并记录四个地址值,再把复制长度暂时缩短到几个 word。如果短复制仍会锁死,优先检查段地址、偏移、当前 VGA 模式,以及 inline assembler 对这些符号的解释。如果短复制正常,再逐步增加长度,并确认缓冲区边界。
如果 movedata() 运行稳定,可以暂时用它作对照。确认地址与长度完全一致后,再判断改用 inline assembly 是否有实际收益。手写 rep movsw 不一定比库函数快。
备注:内容仅供参考。
