签到天数: 2012 天 [LV.Master]伴坛终老
|
堆栈及其使用(举例篇)- ?6 G( J% Q: p4 Y; w# N
例如,在一个子程序中要用到寄存器A,X,Y和P。在进入子程序之后,首先要将这些寄存器的值存入堆栈保存起来,这称为保留现场。子程序的工作进行完准备返回主程序时,还要把原先保留起来的数据从堆栈中取出来,恢复各寄存器原先的状态,这称为恢复现场。保留现场与恢复现场时,对各寄存器进行操作的顺序是不能随意安排的,以免造成混乱。再有就是进栈与出栈的顺序恰好相反。
% H8 }$ G" p: |- N1 D" S/ Z 子程序的起、止部分应当这样设计:( U$ _7 v& q3 W7 `. ] W" n+ J
SUB PHP ;P必须首先进栈
6 S# E4 U! _1 N$ @5 a4 D PHA ;A进栈应在X,Y进栈之前* v) y& z2 I6 ?) I. @+ a. u- q
TXA 3 B; {# x0 `! r; m. {* \3 Y
PHA ;X进栈要通过累加器A1 ?: j0 s p3 C* W- F* h* L4 M
TYA
0 [- j4 u) Q+ ]' K- i PHA ;Y进栈也要通过累加器A( {3 q; A1 t) q. T) w
....;子程序的工作部分& Y2 }7 G, t9 Q6 ~! d5 V
PLA- ~- \. a1 J" Z2 g& {. ~
TAY ;Y最后进栈故最先出栈,Y出栈经过A7 K" f$ z4 ]8 j0 e1 `) f' x
PLA# L1 L: K8 ~0 T w- r* H z
TAX ;X出栈也经过A
7 H2 }4 Q' y+ Y) n PLA ;A出栈
+ K" z k' J" z- D4 K PLP ;P最后出栈,否则其他指令会影响P的某些标志位: {! Y6 T, g1 C, `, z& j3 h
RTS ;恢复现场后可以返回主程序
, J2 }7 y: D& `. ~ T) U' P 6502在子程序执行完毕,遇到RTS指令时是怎样返回主程序的?我们知道,在这种非顺序执行的地方,必须给出跳转的目的地址,这个地址进入程序计数器PC后才能转向。那么这个跳转目的地址是如何得到的呢?原来,当执行JSR指令时,返回地址(也就是JSR指令码所在储存单元地址+2,或者说是JSR指令的第三字节的代码所在地址)自动从程序计数器PC压入堆栈。执行RTS指令时,要将返回地址从堆栈弹出送入程序计数器PC,PC值再自动加1,以指向下一条指令操作码的地址,因此必须记住这条规则:在执行RTS指令之前把在子程序中进栈的数据全部取出,但也不能从栈中取出过多的数据,否则会把返回地址丢掉。5 W# P, g6 W$ C7 B6 @# A8 t
自顶向下的程序设计方法有一条非常重要的原则:子程序应该只有一个入口和一个出口,假如有多个出口,则容易出现的错误之一就是可能忘记将子程序中进栈的内容从堆栈中取出,造成返回地址混乱。解决的办法也很简单:不使用很多RTS指令,而在需要返回的分支处用JMP指令跳转到唯一的一个出口处。5 N! t5 F f/ @8 b w1 z, Z
为了方便,把保留现场与恢复现场这两个工作分别设计成子程序,是经验不足的程序员可能出现而又难于侦错的错误之一。' D, }+ H/ Q/ R* j
例:
( _$ u; [$ l, d1 X+ I' iSUB JSR SAVE ;; q- ]* J% L6 w4 Q* J! P- r
....... ;5 Q/ c% v) `9 o. g# g6 v
JSR RESTORE ;
- ?$ T' H6 a$ p* F8 y& t) K- o2 H RTS
) Z. v( @& W9 F& I1 f5 W: c9 ESAVE PHP ;1 p" l2 ^0 x2 s5 R& _* f9 L
PHA
) S: I' h9 Q! l6 m: g TXA9 X$ K9 j$ M5 r
PHA- @& [9 x }7 F3 z
TYA
! c% u9 q, O% L/ ?9 E/ I4 Y/ I9 J PHA+ }, b. v, _. J% j* U5 @
RTS1 @: T, s8 z0 X) x& {
RESTORE PLA
. s! ?4 c5 r" T TAY
9 r5 r0 a6 z4 ~9 T* n4 a PLA, [, I" Z+ Z+ b1 y
TAX8 h; f4 [% A6 }" F# V* u- Q, Q
PLA
7 m5 W' Y8 t3 T) M3 H2 ?- d PLP
4 m' d2 f. P* l3 W RTS
5 ]! O! Z2 s7 i% ~$ T) f- R 这样编写程序乍看起来是合理的,但实际执行的效果却与预想相差甚远:调用保留现场子程序的JSR指令指令把返回地址压入堆栈,然后转到SAVE子程序,将P、A、X、Y寄存器的值相继相继压入栈内,所以栈顶二数为Y、X的值。当SAVE子程序的返回指令RTS被执行时,6502从堆栈顶取出两个单元的内容作为返回地址送入PC计数器。而这两个单元中的内容根本不是返回地址,而是X、Y被保存起来的值,因此程序会返回到何处大概只有老天才能知道了。
) F) D6 a4 ~8 v o' U" | 同样的,恢复现场子程序也不能使用,因为进入RESTORE子程序时堆栈顶部存放的是返回地址而不是X、Y寄存器的值。
/ C3 }& G, h7 }- f 不过从这个错误,我们可以得到一点启发:堆栈可以作为中继站,使数据能够在PC计数器与其它寄存器之间交换。1 V. u% o ?! _# U0 V [* G2 D. s' b' {8 X
-------by sossb |
|