我们给子程序传递参数时,可以用栈,如下列程序
备注:下面的程序,我们假设初始的sp=10,ss=0001
mov ax,1
push ax ;(sp)=(sp-2)=10-2=E, (ss:[sp])=(ss:[E])=1
mov ax,2
push ax ;(sp)=(sp-2)=E-2=C, (ss:[sp])=(ss:[C])=2
mov ax,3
push ax ;(sp)=(sp-2)=C-2=A, (ss:[sp])=(ss:[A])=3
call addsub ;(sp)=(sp-2)=A-2=8, (ss:[sp])=(ss:[8])=(下一个指令的ip)
add sp,6 ;(sp)=(A+6)=10 还原为初始值
addsub:
mov ax,ss:[sp+2] ;(ax)=(ss:[sp+2])=(ss:[8+2])=(ss:[A])=3
add ax,ss:[sp+4] ;(ax)=3+(ss:[sp+2])=3+(ss:[8+4])=3+(ss:[C])=5
add ax,ss:[sp+6] ;(ax)=5+(ss:[sp+2])=5+(ss:[8+6])=5+(ss:[E])=6
ret ;(ip)=(栈顶的值),(sp)=(sp+2)=A
上面还原 sp 的方法为
外平栈,下面这种方法用到 ret n ,即
内平栈
mov ax,1
push ax ;(sp)=(sp-2)=10-2=E, (ss:[sp])=(ss:[E])=1
mov ax,2
push ax ;(sp)=(sp-2)=E-2=C, (ss:[sp])=(ss:[C])=2
mov ax,3
push ax ;(sp)=(sp-2)=C-2=A, (ss:[sp])=(ss:[A])=3
call addsub ;(sp)=(sp-2)=A-2=8, (ss:[sp])=(ss:[8])=(下一个指令的ip)
addsub:
mov ax,ss:[sp+2] ;(ax)=(ss:[sp+2])=(ss:[8+2])=(ss:[A])=3
add ax,ss:[sp+4] ;(ax)=3+(ss:[sp+2])=3+(ss:[8+4])=3+(ss:[C])=5
add ax,ss:[sp+6] ;(ax)=5+(ss:[sp+2])=5+(ss:[8+6])=5+(ss:[E])=6
ret 6 ;(ip)=(栈顶的值),(sp)=(sp+2)=A,(sp)=(sp+6)=10 还原为初始值
但实际程序中,往往需要暂存寄存器,所以用sp定位方式也得变化,如下:
mov ax,1
push ax ;(sp)=(sp-2)=10-2=E, (ss:[sp])=(ss:[E])=1
mov ax,2
push ax ;(sp)=(sp-2)=E-2=C, (ss:[sp])=(ss:[C])=2
mov ax,3
push ax ;(sp)=(sp-2)=C-2=A, (ss:[sp])=(ss:[A])=3
call addsub ;(sp)=(sp-2)=A-2=8, (ss:[sp])=(ss:[8])=(下一个指令的ip)
add sp,6 ;(sp)=(A+6)=10 还原为初始值
addsub: ;这里我们假设子程序中还有其他指令,所以要暂存相关寄存器
push bx ;(sp)=(sp-2)=8-2=6, (ss:[sp])=(ss:[6])=(bx)
push cx ;(sp)=(sp-2)=6-2=4, (ss:[sp])=(ss:[4])=(cx)
push dx ;(sp)=(sp-2)=4-2=2, (ss:[sp])=(ss:[2])=(dx)
;....假设省略了其他指令,注意下面用sp定位之前传递入栈的参数方式变化
mov ax,ss:[sp+6+2] ;(ax)=(ss:[sp+8])=(ss:[2+8])=(ss:[A])=3
add ax,ss:[sp+6+4] ;(ax)=3+(ss:[sp+2])=3+(ss:[2+A])=3+(ss:[C])=5
add ax,ss:[sp+6+6] ;(ax)=5+(ss:[sp+2])=5+(ss:[2+C])=5+(ss:[E])=6
pop dx ;(sp)=(sp+2)=2+2=4
pop cx ;(sp)=(sp+2)=4+2=6
pop bx ;(sp)=(sp+2)=6+2=8
ret ;(ip)=(栈顶的值),(sp)=(sp+2)=8+2=A
那么上述用sp定位方法很不方便,因为每次都有数据需要进栈暂存,解决方法如下:
;这里为了方便我们假设初始的 SS=0,SP=20H
mov ax,1
push ax ;(sp)=(sp-2)=20-2=1E, (ss:[sp])=(ss:[1E])=1
mov ax,2
push ax ;(sp)=(sp-2)=1E-2=1C, (ss:[sp])=(ss:[1C])=2
mov ax,3
push ax ;(sp)=(sp-2)=1C-2=1A, (ss:[sp])=(ss:[1A])=3
call addsub ;(sp)=(sp-2)=1A-2=18h, (ss:[sp])=(ss:[18h])=(下一个指令的ip)
add sp,6 ;(sp)=(1A+6)=20h 还原为初始值
addsub: ;下面是用bp来定位栈中数据的方法
push bp ;(sp)=(sp-2)=18-2=16h, (ss:[sp])=(ss:[16h])=(bp)
mov bp,sp ;(bp)=(sp)=16h
sub sp,10h ;(sp)=16-10=6h, 这里可以随便减去一个值
;这里我们假设子程序中还有其他指令,所以要暂存相关寄存器
push bx ;(sp)=(sp-2)=6-2=4, (ss:[sp])=(ss:[6])=(bx)
push cx ;(sp)=(sp-2)=4-2=2, (ss:[sp])=(ss:[4])=(cx)
push dx ;(sp)=(sp-2)=2-2=0, (ss:[sp])=(ss:[2])=(dx)
;下面用bp定位栈中数据,bp永远从+4开始,与上面入栈多少数据无关
mov ax,ss:[bp+4] ;(ax)=(ss:[bp+4])=(ss:[16h+4])=(ss:[1A])=3
add ax,ss:[bp+6] ;(ax)=3+(ss:[bp+4])=3+(ss:[16+6])=3+(ss:[1C])=5
add ax,ss:[bp+8] ;(ax)=5+(ss:[bp+8])=5+(ss:[16+8])=5+(ss:[1E])=6
pop dx ;(sp)=(sp+2)=0+2=2
pop cx ;(sp)=(sp+2)=2+2=4
pop bx ;(sp)=(sp+2)=4+2=6
mov sp,bp ;等价于add sp,10h, 目的是还原sp,(sp)=16h
pop bp ;(sp)=(sp+2)=18h
ret ;(ip)=(栈顶的值),(sp)=(sp+2)=18+2=1A
在这个基础上,再去理解附注4就很简单了:
附注4最后那个C程序转换的汇编程序中:
mov sp,bp 为的就是还原sp,避免万一子程序中有栈操作改变了sp,所以最后必须还原,相当于清除自身临时变量。否则没有这句话保护可能导致整个程序崩溃。
至于 add sp,6 为的是平衡栈,因为之前sp减了6,将栈还原到初始状态
inc word ptr [bp-2] 对应 C++