ITEEDU

5.1.2 通过堆栈传递参数

传递数据的第二种常用方法是通过堆栈:先将要传送的数据推入堆栈,再调用子过程,子过程从堆栈中取得所需的数据进行处理。请看下面的程序:

STRINGUP			
        data  segment
              assume    ds:data
        mess  db        'welcome to pc world',0dh,0ah,24h		;定义一个全部为小写的字符串
        data  ends
			
        code  segment
              assume    cs:code
        main  proc      far
              mov       ax,data	;初始化DS寄存器指向数据段
              mov       ds,ax
			
              mov       dx,offset mess	;DX寄存器指向字符串首地址
              mov       ah,9	;选择DOS API的09功能
              int       21h	;显示字符串MESS
              push      dx	;通过堆栈传送字符串首地址
              call      change	;调用CHANGE子过程
              mov       ah,9	;选择DOS API的09功能
              int       21h	;输出大写的字符串
			
              mov       ah,4ch	;选择DOS API的4CH功能
              int       21h	;结束进程
        main  endp
			
      change  proc      near	;CHANGE子过程
              push      bp	;保存BP寄存器至堆栈
              mov       bp,sp	;BP寄存器装入当前堆栈指针
              mov       bx,[bp+4]	;从堆栈中取得字符串的首地址
              mov       cx,19	;转换19个字符
     chloop:
              mov       al,byte ptr [bx]	;从字符串中取一个字符
              cmp       al,20h	;是空格吗?
              jz        continue	;是空格则转CONTINUE

CPU执行此指令时会把AL中的数作为偏移量从BX指向的表中取出一字节数据并以此取代AL中的数据。这条指令有些绕,应用起来也有些麻烦:

              sub       al,20h	;将小写字母转换成大写
              mov       [bx],al	;将大写字母重新存入字符串
   continue:
              inc       bx	;字符串指针加1
              loop      chloop	;处理下一个字符
			
              pop       bp	;恢复BP寄存器的原值
              ret       2	;返回主过程,并清空堆栈
      change  endp
        code  ends
              end       main

 这个程序演示了如何将小写字母转换为大写字母。在主过程中使用了"PUSH DX"指令将DX寄存器中存储的字符串首地址压入堆栈,然后调用CHANGE子过程。那么CHANGE子过程又是如何得到字符串地址的呢?

首先可以肯定地说直接用"POP"指令不可能从堆栈中取得正确的字符串首地址,这是因为执行"CALL"指令时堆栈中存入了主过程的返回地址。习惯上我们常用"基指针寄存器" --BP来取得数据。

BP(BASS POINT)寄存器是一个十六位寄存器,它和BX寄存器一样可用于完成间接寻址,不过BP寄存器无法分成两个八位寄存器来使,而且它和BX寄存器也不完全一样。当我们使用BX做间接寻址时,如果不指定段寄存器,则CPU将默认DS寄存器的值为段地址,而使用BP做此工作时,CPU会默认SS的值为段地址,即BP寄存器常用于在堆栈中取得数据时应用。不难想象P寄存器为我们灵活处理堆栈中的数据提供了便利,这使我们可以不必顾及堆栈数据"后入先出"的规矩,也不必冒险修改SP寄存器。

需要说明的是BP也可以做其它的工作,比如暂存数据,或在其它段中完成间接寻址,只需注意明确给出段寄存器即可。一般来讲BP寄存器更多地用于取得参数,特别是当我们用汇编语言为一些高级语言编制子模块的时候,这几乎是唯一的方法。因为高级语言在调用子过程时几乎都用堆栈传递数据。
子过程中的"RET"指令有些不同寻常,它带了一个立即数。由于我们在调用子过程之前将一个数据压入了堆栈,显然子过程返回后这个数已经没有用了,所以我们要调整堆栈指针寄存器SP的值,以恢复堆栈的原始状态。

采用带常数的RET指令是个比较简单的方法,CPU在执行这样的RET指令时会在返回主过程后自动地将SP寄存器减掉这个常数值,堆栈中的参数也就算自动出栈了。不难想到这个常数在数值上应该等于"参数个数×2"。