1.过程定义伪操作
过程名 PROC 属性
…
过程名 ENDP
其中过程名为标识符,它又是子程序入口的符号地址。属性是指类型属性们可以为NEAR/FAR
(1)NEAR属性:调用程序和子程序在同一个代码段中,(段内调用)
(2)FAR属性,调用程序和子程序不在同一个代码段中(段间调用)
2.子程序的参数传送
2.1通过寄存器传送参数
在例1与例2中,都是通过bx寄存器,在不同的过程中进行传递数值
例1:十六进制到十进制的转换。要求从键盘输入的0-FFFFH的正整数转换为十进制数并在屏幕上显示出来(通过寄存器传送参数)
详细解读
hexidec segment
assume cs:hexidec
main proc far
push ds
mov ax,0
mov ds,ax
repeat:
call hexibin
call crlf
call binidec
call crlf
jmp repeat
ret
main endp
hexibin proc near
mov bx,0
newchar:
mov ah,2
int 21h
sub al,30h
jl exit
cmp al,10
jl add_to
sub al,27h
jl exit
cmp al,10h
jge exit
add_to:
;这里得注意先左移还是先加,应该是先左移,再相加,实际上是左移三次,加法四次,如果左移四次,那么第一次的结果就被移出了。所以需要损耗一次
mov cl,4
shl bx,cl
mov ah,0
add bx,al
jmp newchar
exit:
ret
hexibin endp
binidec proc near
mov cx,10000d
call dec_div
mov cx,1000d
call dec_div
mov cx,100d
call dec_div
mov cx,10d
call dec_div
mov cx,1d
call dec_div
ret
binidec endp
dec_div proc near
mov ax,bx
mov dx,0
div cx
mov bx,dx
mov dl,al
add dl,30h;即使我们知道商在低位AX当中,但是输出的时候需要ascll码,因此还要加上30h
mov ah,2
int 21h
ret
dec_div endp
crlf proc far
mov al,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
ret
crlf endp
hexidec ends
end main
例2:十进制到十六进制的转换程序,程序要求从键盘取得一个十进制数,然后该数以十六进制形式在屏幕上显示出来
decihex segment
assume cs:decihex
main proc far
repeat:
call decibin
call crlf
call binihex
call crlf
jmp repeat
main endp
decibin proc near
mov bx,0
newchar:
mov ah,1
int 21h
sub al,30h
jl exit
cmp al,9
jg exit
cbw
xchg ax,bx
mov cx,10
mul cx
xchg ax,bx
add bx,ax
jmp newchar
exit:
ret
decibin endp
binihex proc near
mov ch,4
mov cl,4
again:
rol bx,cl
mov al,bl
and al,0fh
add al,30h
cmp 3ah
jl prinit
add al,7
prinit:
mov dl,al
mov ah,2
int 21h
dec ch
jnz again
ret
binihex endp
crlf proc far
mov al,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
ret
crlf endp
decihex ends
end main
2.2 通过存储器传送参数
在例3中过程PROADD直接访问模块的数据 区
例3:主程序MAIN和过程PROADD在同一源文件当中,要求用过程累加数组中的所有元素,并把和(不考虑溢出的可能性)送到指定的存储单元中去。
data segment
ary dw 1,2,3,4,5,6,7,8,9,10
count dw 10
sum dw ?
data ends
code segment
main proc far
assume cs:code,ds:data
mov ax,data
mov ds,ax
call proadd
mov ax,4c00h
int 21h
main endp
proadd proc near
push ax
push cx
push si
lea si,ary
mov cx,count
mov ax,0
add_to:
add ax,ary[si]
add si,2
loop add_to
mov sum,ax
pop si
pop cx
pop ax
ret
proadd endp
code ends
end main
然而在过程proadd中,使用了lea si,ary
因此直接访问内存变量,那么如果此时需要累加数组ary1
则会出现累加ary和数组ary1不能用同一个子程序proadd
2.3通过地址表传送参数地址
依然是刚刚的例题,累加数组中的元素
data segment
ary dw 10,20,30,40,50,60,70,80,90,100
count dw 10
sum dw ?
table dw 3 dup(?);地址表
data ends
code segment
main proc far
assume cs:code,ds:data
push ds
sub ax,ax
push ax
mov ax,data
mov ds,ax
mov table,offset ary
mov table+2,offset count
mov table+4,offset sum
mov bx,offset table
call proadd
ret
main endp
proadd proc near
push ax
push cx
push si
push di
mov si,[bx];值得注意的是这是寄存器间接寻址方式,bx寄存器存放的是存储单元的地址,因此是将bx寄存器里地址指向的存储单元赋值给si
;而这个存储单元里面存储的依旧是地址,因此后面出现[si]进一步寻址
mov di,[bx+2]
mov cx,[di]
mov di,[bx+4]
xor ax,ax
next:
add ax,[si]
add si,2
loop next
mov [di],ax
pop di
pop si
pop cx
pop ax
ret
proadd endp
code ends
end main
2.4通过堆栈传送参数地址
data segment
ary dw 10,20,30,40,50,60,70,80,90,100
count dw 10
sum dw ?
data ends
stack segment
dw 100 dup(?)
tos label word
stack ends
在这里:dup为重复的意思,在堆栈段中开辟了200个字节(100个字)。tos(top of stack),并非关键字,tos label word 是伪指令,label的意义是指向下一个要被赋予地址的地方,并且并不分配空间。LABEL 伪指令可以插入一个标号,并定义它的大小属性,但是不为这个标号分配存储空间。
如图,会提示没有堆栈段,为了解决这个问题,使用stack关键字:stack segment stack(后面的是关键字),有了这个关键字,也不需要手动为ss和sp赋初值了。
code1 segment
main proc far
assume cs:code1,ds:data,ss:stack
start:
mov ax,stack
mov ss,ax
mov sp,offset tos
;这里需要初始化ss寄存器和sp寄存器(也就是栈顶指针)
mov ax,data
mov ds,ax
;初始化data段
mov bx,offset ary
push bx
mov bx,offset count
push bx
mov bx,offset sum
push bx
;压入堆栈
call far ptr proadd
;在分析堆栈情况时,call会调用push,并且由于这里是段间直接远调用
;因此会先后push cs;push ip
mov ax,4c00h
int 21h
main endp
通常用一个叫做栈基址(bp)的寄存器来保存正在运行函数栈帧的开始地址,由于栈指针(sp)始终保存的是栈顶的地址,所以栈指针保存的也就是正在运行函数栈帧的结束地址。
code2 segment
assume cs:code2
proadd proc far
push bp
mov bp,sp;sp会随着push和pop指令自动的增加与减少
push ax
push cx
push si
push di
mov si,[bp+0ah];这里依然得注意这里si里的内容是array的偏移地址
mov di,[bp+8]
mov cx,[di]
mov di,[bp+6]
xor ax,ax;相当于初始化一样,这个还会把标志位初始化
;XOR/AND/OR
;CF/OF清零,其余根据结果进行判定
next:
add ax,[si]
add si,2;注意这里是2,字
loop next
mov [di],ax
pop di
pop si
pop cx
pop ax
pop bx
ret 6
proadd endp
code2 segment
其实最后的代码主体加法部分已经并不复杂了,但是这里得注意返回的立即数
最后的sp应该指向最下面的tos所指的存储单元
2.5多个模块之间的参数传递
局部符号:在本模块中定义,在本模块中引用的符号
外部符号:在某一模块中定义,在另一模块中引用的符号
public 符号 extern 符号:类型