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

352 lines
9.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 实验五:子程序设计
## 一、实验目的
1. 掌握子程序的基本结构
2. 学会子程序的各种参数传递方法
3. 理解递归调用的概念和实现方法
4. 掌握子程序的嵌套调用
---
## 二、基础性实验
### 实验5_1 子程序设计一:寄存器传参
#### 实验目的
掌握子程序的基本结构,学会通过寄存器传递参数的方法。
#### 实验内容
编写子程序完成两个16位数的相加并将结果通过寄存器返回。
#### 程序代码
```asm
; 实验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寄存器传递字符串地址完成字符串的显示。
#### 程序代码
```asm
; 实验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!,通过递归调用实现。
#### 程序代码
```asm
; 实验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. **模块化设计思想**:子程序是模块化程序设计的基础,可以提高代码的可重用性和可维护性