6502处理器JSR作为函数调用的栈操作问题及优化方案问询
6502中JSR/RTS的函数化编程最佳实践
问题描述
6502的JSR指令会将2字节的返回地址压入栈中,而RTS指令要求返回地址必须位于栈顶才能正确返回。现有如下调用代码与函数实现:
ldx #$00 ldy #$01 lda cursor_x pha lda cursor_y jsr set_sprite_x_y_sa set_sprite_x_y_sa: ;u have to use stack.. ;or ram/rom ;ldx #$00 ;ldy #$01 ;lda cursor_x ;(!) ;pha ;(!) ;lda cursor_y ;(!) ; y position sta oam, X inx ; tile tya ; transfer val y to a sta oam, X inx ; attributes lda #%00000000 ; no flip sta oam, X inx ; x position pla sta oam, X rts
这段代码存在问题:调用时先压入cursor_x和cursor_y,再执行JSR,此时栈内从顶到底依次是返回地址高字节、返回地址低字节、cursor_y、cursor_x。函数内的pla会取出返回地址的低字节而非cursor_x,导致程序崩溃——除非在函数内部重复压入这两个值(标记为(!)的行),但这会造成冗余,违背函数化的初衷。尝试弹出两次参数再压回返回地址,但寄存器资源受限(X必须用于操作OAM),因此需要更高效的解决方案。
解决方案与最佳实践
方案1:使用零页RAM传递参数
6502的零页RAM访问速度最快,适合存储临时参数。调用前将参数存入零页变量,函数直接读取零页即可,完全避免栈操作的干扰:
; 定义零页临时变量(可根据系统空闲零页调整地址) cursor_x_tmp = $00 cursor_y_tmp = $01 ; 调用代码 ldx #$00 ldy #$01 lda cursor_x sta cursor_x_tmp lda cursor_y sta cursor_y_tmp jsr set_sprite_x_y_sa ; 函数实现 set_sprite_x_y_sa: ; y position lda cursor_y_tmp sta oam, X inx ; tile tya sta oam, X inx ; attributes lda #%00000000 sta oam, X inx ; x position lda cursor_x_tmp sta oam, X rts
这种方法逻辑直观,执行效率高,是6502小函数参数传递的首选方案。
方案2:调整参数压栈顺序,匹配函数使用顺序
这是最巧妙的栈传参技巧:调整调用时的参数压栈顺序,让函数内的弹出顺序与参数使用顺序一致,最终栈顶自然保留JSR压入的返回地址,无需额外操作:
; 调用代码:先压入cursor_x,再压入cursor_y(与原调用顺序相反) ldx #$00 ldy #$01 lda cursor_x pha lda cursor_y pha jsr set_sprite_x_y_sa ; 函数实现 set_sprite_x_y_sa: ; y position:弹出栈顶的cursor_y pla sta oam, X inx ; tile tya sta oam, X inx ; attributes lda #%00000000 sta oam, X inx ; x position:弹出栈顶的cursor_x pla sta oam, X rts ; 此时栈顶正好是JSR压入的返回地址,直接执行RTS即可
此方法无需额外RAM或寄存器,仅通过调整压栈顺序就完美解决问题,是栈传参场景下的最优解。
方案3:临时RAM暂存返回地址(特殊场景用)
如果必须维持原参数压栈顺序,可通过零页RAM暂存返回地址,取出参数后再将返回地址压回栈顶:
; 定义零页变量存储返回地址 ret_addr_lo = $02 ret_addr_hi = $03 ; 调用代码(保持原顺序不变) ldx #$00 ldy #$01 lda cursor_x pha lda cursor_y jsr set_sprite_x_y_sa ; 函数实现 set_sprite_x_y_sa: ; 弹出返回地址并存入零页 pla sta ret_addr_lo pla sta ret_addr_hi ; 取出cursor_y并写入OAM pla sta oam, X inx ; tile tya sta oam, X inx ; attributes lda #%00000000 sta oam, X inx ; 取出cursor_x并写入OAM pla sta oam, X ; 将返回地址压回栈顶 lda ret_addr_hi pha lda ret_addr_lo pha rts
这种方法灵活性高,但需要占用额外零页资源,适合无法调整压栈顺序的特殊场景。
总结
- 优先选择零页RAM传参:简单高效,避免栈操作的复杂性。
- 栈传参时优先调整压栈顺序:无需额外资源,是最优雅的解决方案。
- 特殊场景下再使用返回地址暂存的方法:牺牲少量零页资源换取灵活性。
内容的提问来源于stack exchange,提问作者Jandor
相关产品推荐
相关产品推荐

