那些年,我们一起学过的汇编----之子程序设计

简介:

在我的上一篇博文中,我简单的向大家介绍了汇编语言程序设计的三种基本方式。在一个程序中的不同地方,常常需要多次非循环的使用完成特定功能的程序段,这些程序段除了某些变量的赋值不同外,具有相同的指令序列,这时,我们为了减少重复编写程序,缩短目标代码,节省内存空间,把视线这一功能的指令序列组成一个相对独立的程序段。这也就是我们这片文章中所要讨论的子程序。

子程序相当于高级语言(比如C语言)中的过程和函数,在汇编语言中子程序也称为过程。使用子程序的好处:
a、有利于程序模块化、结构化和自顶向下的程序设计方法,简化了程序设计过程。
b、增加了源程序的可读性,便于调试维护
c、减少了目标代码锁占用的空间
d、子程序一旦编制成功,在开发研制各种软件时都可使用,缩短了软件的开发周期。
 
一、子程序的调用与返回
1、子程序的定义
子程序必须定义在一个逻辑段内,子程序的定义由过程定义伪指令PROC/ENDP来实现,它们分别用在程序的子程序的前后,一般格式如下:
 
 
  1. PROC_NAME   PROC    [NEAR/FAR] 
  2. ...... 
  3. PROC_NAME   ENDP 
其中PROC_NAME为子程序名,也极为CALL的操作数,自程序具有3个属性:段属性、偏移量属性和类型属性,段属性表示该子程序所在段的段基值。偏移量属性表示该子程序在段中的偏移量。类型属性也称为距离属性,可以是NEAR或FAR,属性为NEAR的子程序只能在本段内调用,属性为FAR的子程序则可以在本段以内以及其他段中调用。
2、调用指令
当主程序属性是NEAR的子程序时,CPU把当前指令指针IP的内容压入堆栈,作为返回地址保存起来,然后将子程序的偏移量送入IP,当从子程序返回时,将从堆栈弹出2个字节的返回地址送入IP,当调用属性是FAR的过程时,CPU把当前的段寄存器CS与指令指针IP的内容都压入堆栈,作为返回地址保存起来,然后将子程序的段基值与偏移量送入CS与IP,当子程序返回时,将从堆栈弹出4个字节的返回地址分别送入IP与CS。
我们容易知道,当主程序和子程序处于同一逻辑段时,可以把类型属性定义为NEAR,也可以把类型属性定义为FAR,然后进行调用。而当主程序与子程序不在同一逻辑段是,只可把过程的类型定义为FAR,然后调用。
 
二、返回指令
返回指令RET是子程序逻辑上的最后一条指令,也就是最后一条被执行的指令,它使子程序在完成功能后返回到调用它的CALL指令的后续指令处,即返回地址处继续执行。
 
三、子程序设计的基本要求
1、子程序必须有一定的通用性
2、注意寄存器的保存和恢复
3、正确使用堆栈
4、选用适当的方法在主程序与子程序间进行参数传递
5、编制子程序说明信息文件
 
四、子程序与主程序间的参数传递
在汇编语言中最常用的参数传递方式有3种,分别是:用寄存器传递参数、用堆栈传递参数和用地址表达式传递参数。
 
1、用寄存器传递参数
这种方式是通过通用寄存器来传递的参数,即在主程序调用子程序前,将入口参数送到约定的通用寄存器中,子程序可以直接从这些寄存器中取出参数进行加工处理,并将结果放在约定的通用寄存器中,返回主程序,主程序再从约定的寄存器中取出结果,我们一例子来说明问题:
例:将两个给定的二进制数(8位和16位)转换为ASCII码字符串。
分析:主程序提供呗转换的数据和转化后的ASCII码字符串的存储区的首地址。子程序完成二进制的转换。为了提高子程序的代码转换通用性,它可以完成8位或16位数的转换。设调用子程序时,入口参数为:被转换的数在DX中,若位数小于16,则从高到低存放,转换后的ASCII码的存放首地址在DI中。下面给出一种实现方法:
 
 
  1. DATA    SEGMENT 
  2.     BIN1    DB  35H 
  3.     BIN2    DW  0AB48H 
  4.     ASCBUF  DB  20H DUP  (?) 
  5. DATA    ENDS 
  6. STACK1  SEGMENT PARA    STACK 
  7.     DW  20H DUP  (0) 
  8. STACK1  ENDS 
  9. CODE    SEGMENT 
  10. ASSUME  CS:CODE, DS:DATA, SS:STACK1 
  11. BEGIN:  MOV AX, DATA 
  12.     MOV DS, AX 
  13.     XOR DX, DX 
  14.     LEA DI, ASCBUF      ;存放ASCII码的单元首地址送DI 
  15.     MOV DH, BIN1            ;待转换的第一个数据送DH 
  16.     MOV AX, 8           ;待转换的二进制数的位数送AX 
  17.     CALL    BINASC   
  18.     MOV DX, BIN2 
  19.     MOV AX, 16 
  20.     LEA DI, ASCBUF 
  21.     ADD DI, 8           ;设置下一个数的存放首地址 
  22.     CALL    BINASC 
  23.     MOV AH, 4CH 
  24.     INT     21H 
  25. BINASC  PROC 
  26.     MOV CX, AX 
  27. LOP:    ROL DX, 1           ;最高位移入最低位 
  28.     MOV AL, DL 
  29.     AND AL, 1           ;保留最低位,屏蔽其他位 
  30.     ADD AL, 30H 
  31.     MOV [DI], AL            ;存结果 
  32.     INC DI          ;修改地址指针 
  33.     LOOP    LOP 
  34.     RET 
  35. BINASC  ENDP 
  36. CODE    ENDS 
  37.     END BEGIN 
2、用堆栈传递参数
这种方法是主程序先将入口参数压入堆栈,子程序从堆栈中把参数读出,进行加工处理。这里要注意从堆栈中读取数据与从堆栈中弹出数据是有区别的,从堆栈中读取数据并不改变堆栈的栈顶指针SP,而从堆栈中弹出的数据,则需修改SP,在使用堆栈传递参数时,要保证堆栈状态的正确。
我们还以上面的例子来说明下问题,这次采用堆栈传递参数
分析:如果使用堆栈,一般用包括:
a、在主程序中,将待转换的数据、存放ASCII码的首地址和转换的位数压入栈中
b、在子程序中保存信息
下面我们依然用程序说明问题,在程序的必要处我已经做了注释
 
 
  1. DATA    SEGMENT 
  2.     BIN1    DB  35H 
  3.     BIN2    DW  0AB48H 
  4.     ASCBUF  DB  20H DUP  (?) 
  5. DATA    ENDS 
  6. STACK1  SEGMENT PARA    STACK 
  7.     DW  20H DUP  (0) 
  8. STACK1  ENDS 
  9. CODE    SEGMENT 
  10. ASSUME  CS:CODE, DS:DATA, SS:STACK1 
  11. BEGIN:  MOV AX, DATA 
  12.     MOV DS, AX 
  13.     MOV AH, BIN1 
  14.     PUSH    AX              ;待转换数据压栈 
  15.     MOV AX, 8 
  16.     PUSH    AX              ;待转换位数压栈 
  17.     LEA DI, ASCBUF 
  18.     PUSH    DI          ;存放ASCII码的首地址压栈 
  19.     CALL    BINASC              ;调用转换子程序 
  20.     MOV AX, BIN2 
  21.     PUSH    AX 
  22.     MOV AX, 10H 
  23.     PUSH    AX 
  24.     ADD DI, 8 
  25.     PUSH    DI 
  26.     CALL    BINASC 
  27.     MOV AH, 4CH 
  28.     INT     21H 
  29. BINASC  PROC 
  30.     PUSH    AX 
  31.     PUSH    CX 
  32.     PUSH    DX 
  33.     PUSH    DI   
  34.     MOV BP, SP 
  35.     MOV DI, [BP+10]         ;从堆栈取出入口参数 
  36.     MOV CX, [BP+12] 
  37.     MOV DX, [BP+14]  
  38. LOP:    ROL DX, 1 
  39.     MOV AL, DL 
  40.     AND AL, 1 
  41.     ADD AL, 30H 
  42.     MOV [DI], AL 
  43.     INC DI 
  44.     LOOP    LOP 
  45.     POP DI 
  46.     POP DX 
  47.     POP CX   
  48.     POP AX 
  49.     RET 6           ;返回并从堆栈中弹出6个字节 
  50. BINASC  ENDP 
  51. CODE    ENDS 
  52.     END BEGIN 
3、用地址表传递参数
当要传送的参数较多时,可在主程序中建立一个地址表,在调用子程序前,把所有参数的地址依次存放在该地址表中,然后把地址表的首地址通过寄存器传送到子程序中去,而在子程序中,按照地址表中给出的地址逐个取出参数,用地址表传递参数的方法,在入口参数比较多时很方便,当返回参数较多时,可用同样的方法传递参数,供主程序使用。
 
 
 
     本文转自 驿落黄昏 51CTO博客,原文链接:http://blog.51cto.com/yiluohuanghun/940566,如需转载请自行联系原作者

相关文章
|
7天前
|
存储 C++
C/C++中的整数除法运算与汇编指令DIV和IDIV
C/C++中的整数除法运算与汇编指令DIV和IDIV
16 1