You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 15:53:12