PAYATHON 2026

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 不一定比库函数快。

备注:内容仅供参考。