汇编:模块化程序设计
问题:根据提供的N,计算N的3次方
一、用寄存器来存储参数和结果
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0
mov di,16
;循环处理
mov cx,8
s: mov bx,[si]
call cube
mov [di],ax
mov [di].2,dx
add si,2
add di,4
loop s
mov ax,4c00H
int 21H
cube: mov ax,bx
mul bx
mul bx
ret
code ends
end start
知识点回顾:
dw---定义字(16 位)
dd---定义双字(32 位)
mov ax,data ; 将data段的段地址加载到ax
mov ds,ax ; ds寄存器 = data段的段地址(核心!)
这一步的作用是让 ds 寄存器指向 data 段的起始位置(段地址),后续所有 [si]/[di] 形式的内存访问,默认都是 ds:[si]/ds:[di](即「ds 的段地址 + si/di 的偏移地址」)。
字乘法(16 位 × 16 位 → 32 位)中,执行后的结果,DX寄存器存放32位中的高16位,AX寄存器存放低16位。
代码解释如下:
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8 ; 0~15字节:8个16位整数(待计算立方)
dd 0,0,0,0,0,0,0,0 ; 16~47字节:8个32位存储单元(存立方结果)
data ends
code segment
start:
mov ax,data
mov ds,ax ; 数据段寄存器指向data段
mov si,0 ; si:源数据偏移(初始指向第一个dw)
mov di,16 ; di:目标数据偏移(初始指向第一个dd)
mov cx,8 ; 循环次数:8次(对应8个数字)
s: mov bx,[si] ; 取出当前要计算立方的数(16位)
call cube ; 调用立方计算子程序(结果:dx=高16位,ax=低16位)
; 存储32位结果:低16位存[di],高16位存[di+2]
mov [di],ax ; 立方结果低16位写入目标单元
mov [di+2],dx ; 立方结果高16位写入目标单元+2偏移
add si,2 ; si +=2:指向下一个dw(字占2字节)
add di,4 ; di +=4:指向下一个dd(双字占4字节)
loop s ; 循环直到cx=0
; 程序退出
mov ax,4c00h
int 21h
; 子程序:计算bx中16位数的立方
; 输入:bx = 待计算数(16位)
; 输出:dx:ax = 立方结果(32位,dx高16位,ax低16位)
cube:
mov ax,bx ; ax = bx
mul bx ; ax*bx → dx:ax(先算平方:bx²)
mul bx ; (dx:ax)*bx → dx:ax(再算立方:bx³)
ret ; 返回主程序
code ends
end start
计算完成后,data 段后续双字单元的内容:
| 数字 | 立方值 | 内存存储(十六进制) |
|---|---|---|
| 1 | 1 | 0001 0000 |
| 2 | 8 | 0008 0000 |
| 3 | 27 | 001B 0000 |
| 4 | 64 | 0040 0000 |
| 5 | 125 | 007D 0000 |
| 6 | 216 | 00D8 0000 |
| 7 | 343 | 0157 0000 |
| 8 | 512 | 0200 0000 |
(注:内存中低地址存低字节,因此0001 0000实际存储为01 00 00 00)

二、用内存单元批量传递数据
目的是解决寄存器在数据多的情况下不够的问题
问题:将data段中的字符串转化为大写
assume cs:code ; 声明代码段寄存器cs关联到code段
data segment ; 数据段定义
db 'conversation' ; 定义字节型字符串,共12个字符(c o n v e r s a t i o n)
data ends
code segment ; 代码段定义
start: ; 程序入口
; 初始化数据段寄存器ds
mov ax,data
mov ds,ax ; ds指向data段(8086不能直接mov ds, 立即数,需通过ax中转)
mov si,0 ; 源变址寄存器si初始化,作为字符串偏移地址(从0开始)
mov cx,12 ; 循环计数器cx赋值12(字符串长度)
call capital ; 调用子过程capital,实现小写转大写
; 程序正常退出(DOS中断)
mov ax,4c00H
int 21H
; 子过程:将字符串逐个字符转为大写
capital:
and byte ptr [si],11011111B ; 位运算:将当前字符的第5位(ASCII码小写标识位)置0
inc si ; 偏移地址+1,指向下一个字符
loop capital ; cx-1,若cx≠0则跳回capital开头循环
ret ; 子过程返回(恢复ip,回到call的下一条指令)
code ends
end start ; 程序结束,指定入口为start

可见,已经全部转换为大写了。
三、用栈传递参数
由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数。
问题:计算(a-b)^3,a,b为word型数据
assume cs:code
code segment
start:
mov ax,1
push ax
mov ax,3
push ax
call difcube
mov ax,4c00H
int 21H
difcube: push bp
mov bp,sp
mov ax,[bp+4]
sub ax,[bp+6]
mov bp,ax
mul bp
mul bp
pop bp
ret 4
code ends
end start
代码解释如下:
; 汇编程序段定义:指定代码段寄存器cs指向code段
assume cs:code
; 定义代码段,段名为code
code segment
; 程序入口标签start
start:
; 将立即数1送入ax寄存器 (ax=1)
mov ax,1
; 将ax中的值(1)压入栈中:栈顶地址-2,栈内容[ss:sp]=1
push ax
; 将立即数3送入ax寄存器 (ax=3)
mov ax,3
; 将ax中的值(3)压入栈中:栈顶地址再-2,栈内容[ss:sp]=3
push ax
; 调用difcube子程序:先将当前IP(下一条指令地址)压栈,再跳转到difcube执行
call difcube
; 设置ax=4c00H,准备调用DOS中断退出程序
mov ax,4c00H
; 调用21H号DOS中断,程序正常退出,返回操作系统
int 21H
; 定义difcube子程序:计算(数值差)的立方
difcube:
; 将bp寄存器的值压栈保护(因为要修改bp):栈顶地址-2,保存旧bp值
push bp
; 将栈指针sp的值送入bp,建立栈帧(bp作为栈基址寄存器)
mov bp,sp
; 将bp+4偏移地址的栈内容送入ax:此时栈中bp+4对应压入的3(第二个push的数值)
mov ax,[bp+4]
; ax = ax - [bp+6]:ax = 3 - 1 = 2(bp+6对应压入的1,第一个push的数值)
sub ax,[bp+6]
; 将ax中的差值(2)送入bp寄存器暂存(bp=2)
mov bp,ax
; mul是无符号乘法:ax = ax * bp = 2 * 2 = 4(第一次乘法,平方)
mul bp
; mul是无符号乘法:dx:ax = ax * bp = 4 * 2 = 8(第二次乘法,立方)
mul bp
; 弹出栈中保存的旧bp值,恢复bp寄存器:栈顶地址+2
pop bp
; ret 4:子程序返回,同时将sp += 4(清理栈中之前压入的两个数值,共4字节)
ret 4
; 代码段结束
code ends
; 程序结束,指定入口点为start
end start

先将1压入栈中,再将3压入栈中,然后再将栈基指针BP目前状态的地址压入栈中,执行mov bp,sp是将栈基指针和栈顶指针统一,建立子程序的栈帧;

pop bp恢复子程序运行前的状态
ret 4 的作用:
- 普通 ret 仅弹出返回地址到 IP,sp+2
- ret 4 弹出返回地址后,额外将 sp+4,清理栈中之前压入的两个数值(1 和 3),避免栈泄漏
四、寄存器冲突的问题
子程序标准框架:
子程序开始:子程序中使用的寄存器入栈
子程序内容
子程序使用的寄存器出栈
返回(ret、retf)
; ==============================================
; 功能:将数据段中4个以0结尾的字符串全部转为大写
; 环境:8086汇编,DOS实模式
; 作者:豆包编程助手
; 日期:2025-12-07
; ==============================================
assume cs:code ; 声明代码段寄存器CS关联到code段
; ---------------- 数据段定义 ----------------
data segment
; 定义4个以0结尾的字符串,每个字符串占5字节(4字符+1结束符)
db 'word',0 ; 字符串1:word + 结束符0
db 'unix',0 ; 字符串2:unix + 结束符0
db 'wind',0 ; 字符串3:wind + 结束符0
db 'good',0 ; 字符串4:good + 结束符0
data ends
; ---------------- 代码段定义 ----------------
code segment
start: ; 程序入口点
; 1. 初始化数据段寄存器DS,指向data段
mov ax,data ; 将data段的段地址送入AX(不能直接给DS赋值)
mov ds,ax ; DS = AX,让DS指向数据段首地址
; 2. 初始化循环变量
mov bx,0 ; BX = 0,作为字符串起始偏移量(初始指向第一个字符串)
mov cx,4 ; CX = 4,循环计数器(共处理4个字符串)
s: ; 字符串处理循环入口
mov si,bx ; SI = BX,将当前字符串偏移量送入SI(传递给子程序)
call capital ; 调用大写转换子程序,处理SI指向的字符串
add bx,5 ; BX += 5,指向下一个字符串(每个字符串占5字节)
loop s ; CX--,若CX≠0则跳回s,继续处理下一个字符串
; 3. 程序正常退出(DOS中断)
mov ax,4c00H ; AH = 4CH(DOS退出功能号),AL = 00H(返回码)
int 21H ; 触发DOS中断,结束程序
; ---------------- 子程序:capital ----------------
; 功能:将以0结尾的字符串转为大写(SI指向字符串起始地址)
; 入参:SI = 字符串起始偏移地址
; 出参:无(直接修改原字符串)
; 保护:CX、SI(子程序内会修改,需现场保护)
capital:
push cx ; 保存主程序的CX(循环计数器),保护现场
push si ; 保存主程序的SI(字符串偏移),保护现场
change: ; 字符转换循环入口
; 判断当前字符是否为结束符0
mov cl,[si] ; 取SI指向的字节(当前字符)送入CL
mov ch,0 ; CH = 0,使CX = CL(用于JCXZ判断)
jcxz ok ; 若CX=0(字符为0,字符串结束),跳转到ok
; 小写转大写:ASCII码第5位(二进制)置0
and byte ptr [si],11011111B ; 11011111B = 0xDF,强制第5位为0
inc si ; SI++,指向下一个字符
jmp short change ; 跳回change,继续处理下一个字符
ok: ; 字符串处理完成,恢复现场并返回
pop si ; 恢复主程序的SI,现场还原
pop cx ; 恢复主程序的CX,现场还原
ret ; 子程序返回主程序(回到call capital的下一行)
code ends ; 代码段结束
end start ; 程序入口指向start,汇编结束

浙公网安备 33010602011771号