在8086汇编中,子程序相当于C++中的函数,在此不多叙述定义等其他的。
调用指令 call
CALL指令可分为两类调用:段内调用和段间调用。段内调用是指在同一段的范围之内进行调用,此时只需改变IP寄存器的内容。段间调用则是要转到另一个段去执行子程序,此时不仅要修改IP寄存器的值,还要修改CS寄存器。
段内调用
段内直接调用
格式 : CALL OPR
执行的操作:先保存断点 : SP←SP-2,将CALL的下一条指令的IP人栈;再将子程序名OPR代表的偏移地址→IP, 转到子程序执行。
功能 : 子程序名直接写在指令中,作段内调用。
例 :CALL AAl
段内间接调用
格式 : CALL WORD PTR OPR
执行的操作 : 将断点处的IP人栈保存,如果子程序的偏移地址在16位寄存器中则把寄存器的内容→IP。如果其偏移地址是用存储器中的-一个字指出,则把该存储器单元的内容→IP。
功能 : 子程序的偏移地址由寄存器或存储单元指出,作段内调用。
例 :CALL BX
CALL WORD PTR [BX +SI]
段间调用
段间直接远调用
格式 : CALL FAR PTR OPR
执行的操作 : 先将CALL的下一条指令的CS和IP分别人栈。再把子程序的偏移地址→IP,子程序所在段的段地址→CS。
功能 : 子程序名用FAR PTR属性直接写在指令中,做跨段调用。
-段间间接调用
格式: CALL DWORD PTR OPR
执行的操作:先将CALL的下一条指令的CS和IP分别人栈。再把存储单元的(EA) →IP, (EA +2) →CS。
功能:子程序名保存在双字单元中,第一个字作为偏移地址,第二个字作为段地址,做跨段调用。
返回指令 RET
格式 : RET [n]
段内返回(又称为近返回)时,从堆栈段中弹出的断点仅修改IP;
段间返回(又称为远返回)时,从堆栈段中弹出断点的偏移地址→IP,再弹出断点的段地址→CS;
如果是RET n 指令,表示弹出断点后,再将堆栈指针SP +n之后再返回。
功能 : 用于子程序中,返回到主程序的断点处继续执行。执行时,将断点从栈中弹出,修改IP或修改IP、CS。
例如 : 在子 程序返回时,将堆栈指针加6后返回。
RET 6
子程序又可称为过程。子程序先用过程伪指令定义,得到带有属性的子程序名。其他程序调用该子程序时,系统就会按照NEAR或FAR属性进行转移了。
伪指令PROC
子程序名 PROC 属性
.....
子程序名 ENDP
注意
:PROC和ENDP必须成对使用,表示子程序的开始和结束。属性是指子程序的类型属PROC和ENDP必须成对使用,表示子程序的开始和结束。属性是指子程序的类型属
过程属性
1.主程序和子程序在同一个代码段中则子程序使用NEAR属性。
2.主程序和子程序不在同一个代码段中则子程序使用FAR属性。
3.如果主程序是被执行的第一个程序, 则主程序的属性应该定义成FAR远程的。这是相对于DOS操作系统而言的,在DOS下执行. EXE文件时,是从系统的代码段转人用户的代码段中的,因此用户的主程序应该定义为远程的属性。
4.CALL指令执行时,系统根据子程序名的属性决定保存断点的段地址和偏移地址。
在编写子程序时要注意一个问题,如果主程序用到某些寄存器保存数据,转到子程序后,这些寄存器有可能被改写。或者某些指令必须用特定的寄存器,如乘法、除法指令必须用AX或AL,循环和移位指令必须用CX或CL。
因此在进入子程序时,先要把这些寄存器保存起来,称为现场保护。
一般采用PUSH指令人栈保存
的方法。在子程序返回主程序之前,将堆栈中保存的内容用POP指令弹出
到相关的寄存器中,称为恢复现场。
寄存器传参
设定某些寄存器为传参寄存器,数据由这些寄存器保存,主程序和子程序都可对其读写。利用寄存器传参,不仅可以传送数据,也经常用来传送地址信息。
存储单元传参
当CPU的寄存器使用比较紧张时,采用存储单元传参是-一个较好的解决办法。由于存储单元可以任意定义、用一个单元也可成批使用,主程序和子程序都可访问,因此使用方便。不足之处是,采用存储单元传参,CPU就要通过总线读写存储器,会影响执行速度,降低系统效率。
本人认为这两种传参数的方式都比较好理解,直接取出用即可,没有C++里面的形参实参啥的。
堆栈传参
堆栈是一种特殊的存储结构,利用PUSH人栈和POP出栈指令,可以方便地保存和读取数据。利用堆栈传参需要注意的是栈指针的变化,由于栈指针所指位置就是要索取数据的存储单元,因此必须保证栈指针的正确。
建立学生名次表rank。在以grade为首地址的10个字节的数组中保存了学生成绩(自己任意写一些成绩),grade+i代表学号为i+1的学生的成绩。
要求:建立一个10个字的rank数组,并根据grade中的学生成绩将学生名次填入rank数组中,即rank+i的内容是学号i+1学生的名次。至少要有以下子程序:
如果说排序啥的比较困难,你可以这么想一下,只需获得该学号的成绩,与十个成绩遍历,初始值为1,遇到大于的则加1,即可得出rank数组。
DATAS SEGMENT
GRADE DB 05,10,03,30,29,26,28,31,36,40 ;随便写
RANKNUM DB 10 DUP(?)
MESS1 DB 'DATA IS ',0AH,0DH,'$'
MESS2 DB 0AH,0DH, 'END IS',0AH,0DH,'$'
CENTER DB 1 DUP(?)
NUM DB 1 DUP(?)
DATAS ENDS
STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
CALL MAIN
MAIN PROC NEAR
CALL INPUT
CALL RANK
CALL OUTPUT
MAIN ENDP
INPUT PROC NEAR ;所有的数据
MOV BX,0H
MOV AH,09H
MOV DX,OFFSET MESS1
INT 21H
JMP OUT1
OUT1:
MOV CH,0H
MOV CL,GRADE[BX]
MOV AX,CX
AAM
MOV CX,AX
ADD CX,3030H
MOV AH,02H
MOV DL,CH
INT 21H
MOV DL,CL
INT 21H
MOV DL,32
INT 21H
ADD BX,01
CMP BX,10 ;小于则继续跳转
JL OUT1
INPUT ENDP
RANK PROC NEAR
MOV NUM,00
JMP OUT2
OUT2:
MOV CX,0H
MOV BX,0H
MOV BL,NUM
MOV CL,GRADE[BX]
MOV BX,0H
MOV CENTER,CL ;将比较的数字放到center中
MOV AX,0H
JMP OUT3
OUT3:
CMP BX,10
JGE OUT5 ;大于则跳回前者
MOV CL,GRADE[BX]
CMP CL,CENTER
JG OUT4
ADD BX,1
JMP OUT3
OUT4:
ADD AX,1
ADD BX,1
JMP OUT3
OUT5:
MOV BL,NUM
ADD AL,1
MOV RANKNUM[BX],AL
ADD NUM,01
MOV AL,NUM
CMP AL,10
JL OUT2
RANK ENDP
OUTPUT PROC NEAR ;所有的数据
MOV BX,0H
MOV AH,09H
MOV DX,OFFSET MESS2
INT 21H
JMP OUT6
OUT6:
MOV CH,0H
MOV CL,RANKNUM[BX]
MOV AX,CX
AAM
MOV CX,AX
ADD CX,3030H
MOV AH,02H
MOV DL,CH
INT 21H
MOV DL,CL
INT 21H
MOV DL,32
INT 21H
ADD BX,01
CMP BX,10 ;小于则继续跳转
JL OUT6
OUTPUT ENDP
MOV AH,4CH
INT 21H
CODES ENDS
END START