Files
Operating-System/Experiment/asm-exp/实验五/实验五_子程序设计_报告.md
2026-06-25 00:09:09 +08:00

9.6 KiB
Raw Permalink Blame History

实验五:子程序设计

一、实验目的

  1. 掌握子程序的基本结构
  2. 学会子程序的各种参数传递方法
  3. 理解递归调用的概念和实现方法
  4. 掌握子程序的嵌套调用

二、基础性实验

实验5_1 子程序设计一:寄存器传参

实验目的

掌握子程序的基本结构,学会通过寄存器传递参数的方法。

实验内容

编写子程序完成两个16位数的相加并将结果通过寄存器返回。

程序代码

; 实验5_1: 子程序设计一 - 寄存器传参两个16位数相加
; 功能完成两个16位数的相加
; 入口参数AX, BX
; 出口参数AX结果

DATAS SEGMENT
    A DW 1234H          ; 第一个加数
    B DW 5678H          ; 第二个加数
    RESULT DW ?         ; 结果存储单元
DATAS ENDS

CODES SEGMENT
    ASSUME CS:CODES, DS:DATAS

; 子程序ADD_PROC
; 功能完成两个16位数的相加
; 入口参数AX = 第一个数, BX = 第二个数
; 出口参数AX = 相加结果
ADD_PROC PROC
    ADD AX, BX          ; AX = AX + BX
    RET                 ; 返回调用点
ADD_PROC ENDP

START:
    MOV AX, DATAS
    MOV DS, AX

    MOV AX, A           ; 第一个数送AX
    MOV BX, B           ; 第二个数送BX
    CALL ADD_PROC       ; 调用子程序完成相加
    MOV RESULT, AX      ; 保存结果到RESULT单元

    MOV AH, 4CH         ; 程序退出
    INT 21H
CODES ENDS
END START

程序分析

  1. 子程序结构子程序ADD_PROC包含PROC和ENDP伪指令之间是子程序的功能代码
  2. 参数传递方式采用寄存器传参AX传递第一个加数BX传递第二个加数
  3. 结果返回结果通过AX寄存器返回给调用者
  4. 指令说明
    • CALL ADD_PROC:调用子程序,将返回地址入栈
    • RET:子程序返回指令,从栈中弹出返回地址

验证结果

假设A=1234HB=5678H

  • 入口AX=1234HBX=5678H
  • 执行ADD后AX=68ACH1234H+5678H=68ACH
  • 结果RESULT单元中保存68ACH

实验5_2 子程序设计二DX传参

实验目的

掌握利用寄存器传递参数的方法特别是DX寄存器在字符串显示中的应用。

实验内容

编写子程序利用DX寄存器传递字符串地址完成字符串的显示。

程序代码

; 实验5_2: 子程序设计二 - DX传参字符串显示
; 功能:显示以'$'结尾的字符串
; 入口参数DX = 字符串地址
; 出口参数:无

DATAS SEGMENT
    MSG1 DB 'Hello, World!$'
    MSG2 DB 'Welcome to Assembly!$'
    CRLF DB 0DH, 0AH, '$'    ; 回车换行
DATAS ENDS

CODES SEGMENT
    ASSUME CS:CODES, DS:DATAS

; 子程序DISPLAY
; 功能:显示以'$'结尾的字符串
; 入口参数DX = 字符串地址
; 出口参数:无
DISPLAY PROC
    MOV AH, 09H         ; DOS功能显示字符串
    INT 21H
    RET
DISPLAY ENDP

; 子程序NEWLINE
; 功能:输出回车换行
; 入口参数:无
; 出口参数:无
NEWLINE PROC
    LEA DX, CRLF
    MOV AH, 09H
    INT 21H
    RET
NEWLINE ENDP

START:
    MOV AX, DATAS
    MOV DS, AX

    ; 显示第一条消息
    LEA DX, MSG1
    CALL DISPLAY
    CALL NEWLINE

    ; 显示第二条消息
    LEA DX, MSG2
    CALL DISPLAY
    CALL NEWLINE

    MOV AH, 4CH         ; 程序退出
    INT 21H
CODES ENDS
END START

程序分析

  1. 参数传递方式通过DX寄存器传递字符串的首地址
  2. DOS功能调用使用INT 21H的09H功能显示以'$'结尾的字符串
  3. LEA指令LEA DX, MSG1将MSG1的偏移地址加载到DX
  4. 子程序嵌套主程序调用DISPLAY和NEWLINE两个子程序实现换行显示
  5. 程序结构
    • DISPLAY子程序显示DX指向的字符串
    • NEWLINE子程序输出回车换行0DH=CR0AH=LF

验证结果

运行程序后,屏幕依次显示:

Hello, World!
Welcome to Assembly!

三、加强性实验

实验5_3 子程序设计三:递归阶乘计算

实验目的

掌握较复杂的子程序设计方法,学习递归调用的实现思想。

实验内容

编写子程序计算N!,通过递归调用实现。

程序代码

; 实验5_3: 子程序设计三 - 递归阶乘计算
; 功能计算N!(通过递归调用实现)
; 入口参数CX = N待计算的数
; 出口参数AX = N!结果

DATAS SEGMENT
    N DW 5              ; 要计算阶乘的数
    RESULT DW ?         ; 结果存储单元
DATAS ENDS

STACKS SEGMENT
    DW 100H DUP(?)      ; 堆栈空间,用于递归调用
STACKS ENDS

CODES SEGMENT
    ASSUME CS:CODES, DS:DATAS, SS:STACKS

; 子程序FACTORIAL
; 功能计算N!(递归实现)
; 入口参数CX = N
; 出口参数AX = N!
FACTORIAL PROC
    CMP CX, 1           ; 比较N与1
    JBE DONE            ; 如果N<=1跳转到DONE

    PUSH CX             ; 保存当前N值
    DEC CX              ; 计算N-1
    CALL FACTORIAL      ; 递归调用,计算(N-1)!
    POP CX              ; 恢复N值
    MUL CX              ; AX = AX * CX = (N-1)! * N = N!
    RET

DONE:
    MOV AX, 1           ; 递归终止返回1
    RET
FACTORIAL ENDP

START:
    MOV AX, DATAS
    MOV DS, AX

    MOV CX, N           ; 将N送入CX作为入口参数
    CALL FACTORIAL      ; 调用递归子程序计算阶乘
    MOV RESULT, AX      ; 保存结果

    ; 以下代码用于显示结果将数字转换为ASCII显示
    MOV CX, 0           ; 计数寄存器清零
    MOV BX, 10          ; 除数10

DIVIDE_LOOP:
    XOR DX, DX          ; DX清零32位除法需要
    DIV BX              ; AX = AX / 10, DX = AX % 10
    PUSH DX             ; 保存余数(低位先入栈)
    INC CX              ; 计数+1
    CMP AX, 0           ; 商是否为0
    JNE DIVIDE_LOOP     ; 不为0则继续除法

    ; 显示结果
DISPLAY_LOOP:
    POP DX              ; 弹出数字
    ADD DL, '0'         ; 转换为ASCII字符
    MOV AH, 02H         ; DOS功能显示字符
    INT 21H
    LOOP DISPLAY_LOOP

    MOV AH, 4CH         ; 程序退出
    INT 21H
CODES ENDS
END START

程序分析

  1. 递归终止条件当CX<=1时返回AX=1结束递归
  2. 递归公式N! = N × (N-1)!
  3. 寄存器保护
    • PUSH CX在递归调用前保存当前N值
    • POP CX在递归返回后恢复N值
  4. 乘法运算MUL CX实现AX与CX的16位无符号乘法结果存入AX
  5. 堆栈作用:递归调用时,堆栈保存返回地址和中间变量
  6. 结果显示通过除10取余的方法将二进制结果转换为ASCII码显示

验证结果

以N=5为例执行过程如下

  • 5! = 5 × 4! = 5 × 24 = 120
  • 结果显示为120

四、思考题

4.1 子程序参数传递有哪些方式?各有何优缺点?

1. 寄存器传递方式

  • 优点:执行速度快,不需要额外的存取操作
  • 缺点:寄存器数量有限,只能传递少量参数

2. 堆栈传递方式

  • 优点:参数数量不受限制,可以传递任意多参数
  • 缺点:需要额外的栈操作,速度较慢,编程复杂度增加

3. 存储器传递方式

  • 优点:适合传递大数据块(数组、结构等)
  • 缺点:占用内存空间,需要事先分配存储单元

4. 混合传递方式

  • 优点:综合使用多种传参方式,灵活高效
  • 缺点:设计复杂度增加,需要明确约定各参数的传递方式

4.2 递归调用需要注意什么问题?

1. 递归终止条件

  • 必须设置明确的递归终止条件,避免无限递归
  • 每次递归调用都要趋近于终止条件

2. 现场保护

  • 使用PUSH指令保存可能被修改的寄存器
  • 在递归返回前用POP指令恢复寄存器

3. 堆栈空间

  • 递归深度受堆栈大小限制
  • 递归过深可能导致堆栈溢出

4. 参数传递

  • 确保入口参数在递归调用时正确传递
  • 注意参数的副本性,避免数据共享导致的错误

4.3 如何实现子程序的嵌套调用?

1. 基本原理

  • 子程序A中可以调用子程序B子程序B中又可以调用子程序C
  • 通过CALL指令实现嵌套调用每次调用自动保存返回地址

2. 返回地址管理

  • 每层CALL都会将返回地址压入堆栈
  • RET指令从堆栈弹出返回地址确保正确返回

3. 寄存器保护

  • 在子程序入口处PUSH需要保护的寄存器
  • 在子程序出口处POP恢复寄存器
  • 注意嵌套时寄存器保护的嵌套顺序

4. 示例结构

MAIN:
    CALL SUB1
    ...

SUB1:
    PUSH CX             ; 保护寄存器
    CALL SUB2           ; 嵌套调用子程序2
    POP CX              ; 恢复寄存器
    RET

SUB2:
    PUSH CX
    CALL SUB3           ; 嵌套调用子程序3
    POP CX
    RET

五、实验总结

  1. 子程序的基本结构子程序由PROC/ENDP伪指令定义使用RET指令返回调用点

  2. 参数传递的重要性:参数传递是子程序设计的核心,决定了主程序与子程序之间的数据交换方式

  3. 寄存器传参的特点简单直接适合少量参数1-3个但受寄存器数量限制

  4. 堆栈传参的特点:适合参数较多的情况,可以传递任意数量参数,但需要谨慎管理栈平衡

  5. 递归调用的要点

    • 必须有明确的递归终止条件
    • 需要妥善保护现场(寄存器)防止数据被破坏
    • 递归深度受堆栈空间限制,应注意避免堆栈溢出
  6. 嵌套调用的实现通过CALL指令和RET指令配合实现每层调用都有自己的返回地址

  7. 模块化设计思想:子程序是模块化程序设计的基础,可以提高代码的可重用性和可维护性