68000汇编:排序子程序中栈传递参数的实现与疑问
68000汇编栈传参与交换子程序问题解答
1. 栈传递参数的方式是否正确?潜在问题有哪些?
你的参数压栈逻辑(用pea.l依次压入两个元素地址)本身是可行的,但子程序内的栈偏移计算完全错误,这会导致你取到的不是传入的参数地址,而是之前压栈的寄存器值,直接引发逻辑错误。
具体来说,进入SWAP_SUB后,你先执行了movem.l d3-d4,-(a7),把两个寄存器压入栈(占8字节),再加上bsr指令自动压入的4字节返回地址,实际传入的参数地址应该在A7+12和A7+16的位置,而不是你写的4(a7)和8(a7)。
除了这个直接错误,这种手动计算栈偏移的方式还有几个潜在问题:
- 偏移易出错:如果后续修改子程序内的压栈操作(比如增减保存的寄存器),所有参数偏移都要重新计算,很容易遗漏。
- 栈平衡风险:调用者手动用
addq.l #8,a7平衡栈,若参数数量或长度变化,忘记调整这个值会导致栈溢出或混乱。 - 参数顺序混淆:
pea是先压入的参数在栈的更深处,子程序取参数时要注意顺序,稍不注意就会把两个地址搞反。
2. 子程序传递参数的通用栈设置方式
68000汇编里常用两种栈传参规范,还有更可靠的栈帧写法:
- C调用约定(cdecl):
- 参数从右到左依次压栈
- 调用者负责调用后平衡栈(比如你现在用的
addq.l #8,a7) - 适合参数数量不固定的场景
- Pascal调用约定:
- 参数从左到右依次压栈
- 被调用者用
rts #n指令平衡栈(n是参数总字节数,比如rts #8) - 调用者无需手动调整栈,更安全
- 栈帧(Link指令):
这是最推荐的写法,用link a6,#0建立栈帧,把A6作为基址寄存器访问参数:
这种写法的好处是,不管子程序内压了多少寄存器,参数的偏移量永远固定,完全避免手动计算A7偏移的错误。SWAP_SUB: link a6,#0 ; 压入旧A6,将A6设为当前栈指针 move.l 8(a6),a0 ; 第一个参数(返回地址在4(a6),旧A6在0(a6)) move.l 12(a6),a1 ; 第二个参数 ; ... 子程序逻辑 ... unlk a6 ; 恢复A6和栈指针 rts #8 ; 同时平衡栈(如果用Pascal约定)
3. 代码能否简化?
可以大幅简化,主要有几个优化点:
(1)交换逻辑极度简化
你当前的交换代码用了一堆移位指令,完全没必要——直接用两个寄存器暂存字节值,互相赋值即可:
move.b (a0),d3 move.b (a1),d4 move.b d4,(a0) move.b d3,(a1)
这几行代码和你原来的复杂移位逻辑完全等价,而且可读性和执行效率都更高。
(2)栈处理优化
要么用栈帧写法避免偏移错误,要么干脆用寄存器传递参数(既然调用前已经有A0指向数组元素,直接把两个地址放到A0和A1里,不用压栈,效率更高):
DO_SWAP: movea.l a0,a1 ; a1 = 当前a0的地址 subq.l #1,a1 ; a1 = 前一个元素地址 bsr.s SWAP_SUB bra.s NO_SWAP SWAP_SUB: ; a0和a1已经是两个元素的地址,直接交换 move.b (a0),d3 move.b (a1),d4 move.b d4,(a0) move.b d3,(a1) rts
这种方式完全省去了栈操作,执行效率最高,也避免了栈相关的错误。
(3)其他小优化
- 排序循环里的
moveq.l #len-2,d6可以保留,但如果len是常量,也可以直接写moveq.l #3,d6(因为len=5,len-2=3),不过保持可读性更重要。 - 可以把
SIMHALT换成stop #$2700(标准的68000停机指令),兼容性更好。
内容的提问来源于stack exchange,提问作者Pato
相关产品推荐
相关产品推荐

