汇编实现贪吃蛇游戏栈操作异常及运行问题求助
问题描述
在课堂上编写了一款贪吃蛇游戏,此前学习了栈段(Stack Segment)的push、pop操作以及黑盒封装知识,尝试将栈操作应用到贪吃蛇项目中后,代码长度增加且程序无法正常运行,需要定位问题。
具体疑问:
- 老师提到任何proc都要以push开头、pop结尾,如果在当前proc内部调用其他proc,是需要遵循同样的入栈出栈规则,还是要先完成出栈再调用下一个proc?
- 如何确定具体是哪部分代码出错?
当前异常表现:程序在DOSBOX中编译时提示0警告0错误,但运行时贪吃蛇无法移动,随机生成的道具也不显示。
问题代码
MODEL small STACK 100h DATASEG snake_length dw 3 object dw ? Clock equ es:6Ch message db 'game over, do you want to play again$' ; -------------------------- CODESEG proc cleanscreen push cx push bx push ax xor ax,ax mov cx,4000d mov bx,0 clean: mov [es:bx],al inc bx loop clean pop ax pop bx pop cx ret endp cleanscreen proc playgame ;starts the game push ax mov ax,0b800h mov es,ax pop ax call cleanscreen push bx push dx mov dh, 200 mov dl, '*' xor bx,bx mov di,2000d mov [es:di],dx mov [es:di-2],dx mov [es:di-4],dx mov [word ptr bx], 1996d mov [word ptr bx+1], 1998d mov [word ptr bx+2], di pop dx pop bx call random call movement ret endp playgame proc movement mov ah,1 int 16h jnz skip mov ah,0 int 16h skip: cmp al, 'w' je wp cmp al, 'a' je ap cmp al, 'd' je dpointer cmp al, 's' je spo cmp al, 'q' je endgame call movement wp: sub di,160d jmp update spo: add di, 160d jmp update dpointer: add di,2d jmp update ap: sub di,2d update: call bounderies ret endp movement proc bounderies push ax push dx push bx cmp di,160d jb endgame cmp di, 38940 ja endgame mov ax,di mov bl, 160d div bl cmp ah,0 je endgame inc ax mov ax,di div bl cmp ah,0 je endgame pop bx pop dx pop ax call update_snake ret endp bounderies proc update_snake push bx push si push cx mov cx, offset snake_length xor bx,bx xor ch,ch mov si,[bx] mov [es:si], ch transition: mov si,[bx+1] mov [bx],si loop transition mov [bx], di pop cx pop si pop bx call delay ret endp update_snake proc delay push cx push bx mov cx, 0FFFFh lopa: mov bx, 50 lopb: dec bx cmp bx, 0 jnz lopb loop lopa ret pop bx pop cx call check_if_eaten endp delay proc endgame ret endp endgame proc check_if_eaten cmp di, [object] jne restfunc call random restfunc: call movement ret endp check_if_eaten proc random push ax push bx mov ax, [Clock] ; read timer counter mov ah, [byte cs:bx] ; read one byte from memory xor al, ah ; xor memory and counter and al, 00001111b ; leave result between 0-15 sal al,1 mov [byte ptr object], al pop bx pop ax ret endp random start: mov ax, @data mov ds, ax call playgame exit: mov ax, 4c00h int 21h END start
问题解答
栈规则说明
你提到的proc入栈出栈规则本质是寄存器保护约定:每个proc在入口处把自己会修改的寄存器push入栈,在返回(ret)之前按相反顺序pop恢复,和proc内部是否调用其他proc无关。内部调用其他proc时,被调用的proc自己会负责保护它用到的寄存器,不需要先把当前proc的栈pop完再调用,只要保证当前proc自己的push和pop配对、在ret前恢复所有入口时保存的寄存器即可。
代码具体错误点
- delay过程栈不平衡+逻辑截断
delay过程中ret写在了pop操作和call check_if_eaten之前,导致栈中残留了push的cx和bx,且后续的道具检测、移动逻辑永远不会执行,程序运行流程完全错乱。
修复方案:把ret移到过程末尾:
proc delay push cx push bx mov cx, 0FFFFh lopa: mov bx, 50 lopb: dec bx cmp bx, 0 jnz lopb loop lopa pop bx pop cx call check_if_eaten ret endp delay
- 蛇身存储地址非法
playgame过程中你用xor bx,bx将bx置0,往[bx]等地址写入蛇身坐标,此时ds指向数据段,地址0属于系统中断向量表,你直接改写了系统内存,蛇身坐标没有正确存储,还会触发系统异常。
修复方案:在数据段定义专门的蛇身存储数组:
; DATASEG 中新增 snake_body dw 100 dup(0)
playgame中写入坐标时替换xor bx,bx为mov bx, offset snake_body。
3. 蛇身更新长度读取错误
update_snake过程中mov cx, offset snake_length是把snake_length变量的地址赋值给cx,而不是读取蛇的长度值,导致循环更新逻辑异常。
修复方案:改为mov cx, [snake_length]。
4. 键盘检测逻辑写反
movement过程中int 16h ah=1的作用是检测是否有按键按下,有按键时ZF=0,你写的jnz skip会导致有按键时跳过读键逻辑,无按键时才读键,逻辑完全相反。
修复方案:改为jz skip。
5. 道具生成逻辑错误+无渲染步骤
random过程中mov ah, [byte cs:bx]的bx没有初始化,读取的内存地址完全随机,且只生成了低8位列坐标,没有行坐标,也没有将道具渲染到显存,所以道具不显示。同时Clock的定义是es:6Ch,调用时需要保证es指向0段才能正确读取系统时钟。
调试定位方法
DOSBOX环境下可以用debug工具加载编译后的程序,用t命令单步执行,逐行查看寄存器、栈、对应内存地址的内容是否符合预期,即可快速定位错误位置。
内容的提问来源于stack exchange,提问作者user17341742

