在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属性直接写在指令中,做跨段调用。

调用devexpress open save 调用子程序指令格式_编程语言

-段间间接调用

格式: 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必须成对使用,表示子程序的开始和结束。属性是指子程序的类型属

调用devexpress open save 调用子程序指令格式_编程语言_02

过程属性

1.主程序和子程序在同一个代码段中则子程序使用NEAR属性。
2.主程序和子程序不在同一个代码段中则子程序使用FAR属性。
3.如果主程序是被执行的第一个程序, 则主程序的属性应该定义成FAR远程的。这是相对于DOS操作系统而言的,在DOS下执行. EXE文件时,是从系统的代码段转人用户的代码段中的,因此用户的主程序应该定义为远程的属性。
4.CALL指令执行时,系统根据子程序名的属性决定保存断点的段地址和偏移地址。

调用devexpress open save 调用子程序指令格式_寄存器_03

三、过程保护

在编写子程序时要注意一个问题,如果主程序用到某些寄存器保存数据,转到子程序后,这些寄存器有可能被改写。或者某些指令必须用特定的寄存器,如乘法、除法指令必须用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学生的名次。至少要有以下子程序:

  • main
  • Input
  • Rank
  • Output

如果说排序啥的比较困难,你可以这么想一下,只需获得该学号的成绩,与十个成绩遍历,初始值为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