汇编:模块化程序设计

问题:根据提供的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 段后续双字单元的内容:

数字立方值内存存储(十六进制)
110001 0000
280008 0000
327001B 0000
4640040 0000
5125007D 0000
621600D8 0000
73430157 0000
85120200 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,汇编结束

posted @ 2025-12-07 15:23  chenlight  阅读(2)  评论(0)    收藏  举报  来源