就我们所编制的这几个程序而言,由于主、子过程均在同一源程序中,所以我们可以直接在子过程中使用那些用DB、DW伪指令定义的变量,也就是将这些变量作为"全局"变量来使用。这可以使我们无需过多考虑参数传递的问题。如果我们要向子过程传递多个分散的数据,则可以采用地址表。下面这个程序是由CALC.ASM变化而来,它和CALC有相同的功能,但请注意参数的传递方式:
CR equ 0dh ;回车符的ASCII码 LF equ 0ah ;换行符的ASCII码 data segment assume ds:data mess db 'welcome to pc world',0dh,0ah,24h ;定义一个字符串 ascii db '01234 56789abcdef' ;十六进制数字对应的字符 sum dw ? ;定义一个WORD变量保存字符总数 addr_tbl dw 2 dup (?) ;定义一个地址表 data ends code segment assume cs:code main proc far mov ax,data ;初始化DS寄存器 mov ds,ax mov word ptr addr_tbl,offset mess ;将字符串偏移地址存入地址表 mov word ptr addr_tbl+2,offset sum ;将SUN的偏移地址存入地址表 mov bx,offset addr_tbl ;BX送入地址表的偏移地址 call calc ;调用CALC子过程 mov ax,sum ;AX寄存器送入字符总数 mov bx,offset ascii ;ASCII表的偏移地址送入BX call hexout ;调用HEXOUT子过程 mov ah,4ch ;结束进程 int 21h main endp calc proc near ;CALC子过程 push bx ;保存BX寄存器 push bp ;保存BP寄存器 mov bp,word ptr [bx] ;将字符串的偏移地址送入BP mov bx,word ptr [bx+2] ;将SUM变量的偏移地址送人BX caloop: mov dl,byte ptr ds:[bp] ;取得一个字符 cmp dl,24h ;是'$'吗? jz return ;若是则转至RETURN mov ah,2 ;显示这个字符 int 21h inc bp ;BP指向下一个字符 inc word ptr [bx] ;SUM变量加1 jmp caloop ;转至CALOOP处理下一个字符 return: pop bp ;恢复BP寄存器 pop bx ;恢复BX寄存器 ret ;返回主程序 calc endp hexout proc near ;HEXOUT子过程 push ax ;保存AX寄存器 mov cx,2 ;显示2位十六进制数 hexloop: push cx ;暂存显示位数 mov cl,4 ;准备循环移位数 rol al,cl ;将AL左循环移4位 push ax ;暂存移位结果 and al,0fh ;保留AL原值的高4位 xlat ascii ;取得对应的ASCII码 mov dl,al ;将数字对应的ASCII码送入DL mov ah,2 ;显示数字 int 21h pop ax ;恢复AX寄存器 pop cx ;恢复CX寄存器 loop hexloop ;循环至HEXLOOP处理下个数位 mov dl,CR ;显示回车符 mov ah,2 int 21h mov dl,LF ;显示换行符 int 21h pop ax ;恢复AX寄存器 ret ;返回主程序 hexout endp code ends end main
程序中DW伪指令的用法有些奇特,一般情况下我们常用DB、DW伪指令定义一些变量,这些变量往往有初值,所以在DB、DW后面总要跟一些字符串或数字。如果所定义的变量无初值,则我们就可以用"DW ?"这样的用法,"?"表示留出相应的存储空间,但不存入数据。不过请不要被"不存入数据"这句话误导,说是不存入数据,实际上这些存储空间将被初始化为"0","D# ?"和"D# 0"是完全一样的。这和我们从堆栈中动态分配到的存储空间不一样,当我们从堆栈中分配到自动变量空间后,我们有时要自己将其初始化为0,仔细研究一下CALC1.ASM不难发现这一点,然而用"D# ?"定义的变量就无需初始化。
新出现的"DUP"(Duplication)是一个操作符:
我们在第四章中谈到过一个分析运算符--OFFSET,用于取得某些变量的偏移地址。现在讨论的"DUP"操作符主要用于生成多个具有相同位宽的变量,用法很简单:
变量名 DB/DW 变量个数 DUP 数值1,数值2,...
"变量个数"应是一个正整数,用来指定括号中的数据项重复次数,请看下例:
data segment var1 db 2 dup ('A') ;定义'AA' var2 db 2 dup ('A','B','C') ;定义'ABCABC' var3 db 2 dup ('A',2 dup('B'),'C') ;定义'ABBCABBC' data ends end
C:\ASM\> DEBUG TEMP.EXE[Enter]? 2 dup ('A',2 dup ('B'),'C') d:cs:0 f[Enter]? 2 dup ('A') 0E79:0000?41 41 41 42 43 41 42 43-41 42 42 43 41 42 42 43?AAABCABCABBCABBC 2 dup ('A','B','C')
仔细看看输出的结果,"DUP"并不难理解。
这个程序应用了地址表(ADDR_TBL)传送数据,而且BP寄存器被用于作一般的间接寻址,请注意指令MOV DL BYTE PTR DS:[BP]中的"DS:"就是用于显式地指定DS寄存器提供段地址,是不能省略的。
在这些过程间传递数据的方法之中,最为常用的是通过寄存器和堆栈传递数据。如果子过程主要是由汇编程序调用,那么通过寄存器传送数据是最简捷快速的方法;如果某子过程是为其它高级语言(如PASCAL、C等)编制,那么就应该通过堆栈取得参数,因为高级语言程序进行过程(函数)调用时都是通过堆栈传递数据的。