几周前,我写了一篇对数据进行结构化访问的文章
寻址方式在结构化数据访问中的应用
还有一篇是对子程序的撰写
编写子程序
在学习了许多各种各样的操作以后,我们今天就可以开始对之前这个实验美化一下了
t.gif


太闪了,其实就是对实验7的数据输出到屏幕中去
题目

结果图

在编写完这个程序之后,或许你会重新燃起对Assembly语言的热爱,我在其中出现了许多小插曲,下面就一一讲解一下思路以及过程

用到的数据段

assume cs:code, ds:data, ss:stack
data segment
        db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984','1985'
        db '1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
 
        dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514,345980
        dd 590827,803530,1183000,1843000,2758000,3753000,4649000,5937000
 
        dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
        dw 11542,14430,15257,17800
data ends

number segment
        db 1024 dup(0)
number ends

stack segment stack                     ;!!!事实证明,后面这个stack加上可以自动赋值sp,不加默认sp从0开始
        db 1024 dup(0)
stack ends

这里,我现在才发现我一直都没有理解这段伪指令的含义,我之前的错误有二:
① 在assume伪指令那行,我之前都是拿空格分隔,导致一直都在报错,但不影响程序的执行(编译器还是有点智能的),实际上应加逗号分隔
② 直接assume数据段就完了?当然不是,为了代码的严谨性,我们在代码段里也应该对使用的数据段重新赋值

我上面代码中对栈的声明就是一个很好的例子,如果我只是使用stack segment声明,而栈顶标记sp就没有赋值,容易产生我进出栈的时候,致使sp为FFFE的情况
没对栈初始化的情况

着重讲讲主要的子程序

延续的还是上一篇文章的子程序,只是我在其中发现了些逻辑上的错误!!!后面单开一篇来讲

  • div_dw(32位除法运算)
          div_dw: 
                  ;(输入):ax低16位,dx高16位,cx为16位除数
                  ;(输出):dx结果高16位,ax结果低16位,cx为余数
                  push bx
    
                  mov bx, ax      ;暂存ax,低位
                  mov ax, dx      ;设置被除数 
                  mov dx, 0       ;高位置为0
                  div cx          ;高16作除法,余数dx并到低16位,这里无需改动,dx也是除法的高位
                  push ax         ;暂存高16位除法结果中的商
          
                  mov ax, bx
                  div cx          ;低16位除法,结果,ax为商,dx为余数
    
                  mov cx, dx      
                  pop dx          ;将暂存的高位ax,置于dx
    
                  pop bx
                  ret
    这个子程序给的参数很妙,不同于自带有的div指令,这个除法能完成所有的32位对16位的除法,只是精度不高
    而自带有的div指令恰恰相反,这里也可以细细揣摩!

  • dtoc(十进制数值转对应字符串)
          dtoc:
                  ;X输入ax,(16位)
                  ;结果保存在number字段
                  ;注意,检测商为0才跳出!!
                  push bx
                  push cx
                  push dx
                  push ds
                  push si
    
                  mov bx, number          
                  mov ds, bx              ;目标地址
    
                  mov si, 0               ;偏移指针,个数
                  mov bx, 10              ;除数
                  mov dx, 0               ;32位除法,但这里只有ax参与,dx得置0
                  
          in_num: 
                  div bx
                  push dx
                  inc si
                  mov cx, ax              ;这里没有loop循环,cx可放心使用
                  jcxz in_num_exit
                  mov dx, 0
                  jmp in_num
    
          in_num_exit:
                  mov cx, si
                  mov si, 0
                  mov bx, 0
          set_num:
                  pop bx
                  add bx, 30h
                  mov ds:[si], bl
                  inc si
                  loop set_num
                  mov byte ptr ds:[si], 0         ;末尾添0,表示字符串结束
    
                  pop si
                  pop ds
                  pop dx
                  pop cx
                  pop bx
                  ret 
    这里得和之前版本区分一下,因为之前那个逻辑是错误的,对于栈的使用导致我写后面子程序的过程中处处碰壁
    列举错误情况1
    把set_num中的婆婆bx去掉.png
没有及时地将数据出栈,导致死循环,也可能会出现各种奇葩的问题,
这告诉我们编写代码的严谨性多么重要,你添一句,删一句,结果都可能出不来
对于这个子程序,唯一的缺点就是输出的数据只能是0~65535(无符号数),因此我对这个子程序改写了一下

  • dtoc_pro(升级版,转换的数值最高可达4294967295,也就是4个FFFF)
          dtoc_pro:
                  ;输入ax低16,dx高16
                  ;内容输出到number字段
                  ;注意,检测商为0才跳出
                  push bx
                  push cx
                  push si
                  push ds
    
                  mov si, 0               ;记录次数
                  mov bx, number
                  mov ds, bx              ;目标地址
    
          in_num_pro: 
                  mov cx, 10              ;除数10
                  call div_dw             ;返回余数cx
                  
                  push cx                 ;保存余数
                  inc si
                  
                  push cx                              
                  mov cx, ax              ;判断商为0
                  or cx, 0
                  or cx, dx 
                  jcxz in_num_pro_exit    ;商为0,跳转,开始存入number字段
                  pop cx
                  
                  jmp in_num_pro
    
          in_num_pro_exit:
                  pop bx                  ;!!!!!!!!!!!注意多余的push要及时出栈,影响数据排布!!!!
                  mov cx, si
                  mov si, 0
                  mov bx, 0
          set_num_pro:
                  pop bx                  ;写入目标地址操作
                  add bx, 30h
                  mov ds:[si], bl
                  inc si             
                  loop set_num_pro
    
                  mov byte ptr ds:[si], 0         ;末尾添0,表示字符串结束
                  
                  pop ds
                  pop si
                  pop cx
                  pop bx
                  ret
    这里没有像普通版(dtoc)那样使用div指令,而是使用我们的指令div_dw,而输出的结果恰好也可以作为下次除法的被除数,可见这个大除法设计的多么巧妙
    稍微注意一下,最后一个字节是添0,可以对比联想一下C语言中字符串的格式

  这里讲讲上次子程序出现的错误之一:我们判断结束的标准应该是商为0,而不是余数为0。如果是使用后者,那么对于十进制数中穿插了0的数字(比如101)则只会输出个位的1
  紧接着问题就来了,对于dtoc,我的商就存储在ax中,直接赋值cx,利用jcxz则可判断,而我们现在遇到的是32位的商,如何解决?
按位运算:
因为我们要判断ax和dx同时为0才做跳转,但此时我们目前能接触到的操作就只有一个jcxz,这就想到了按位运算,而且这里还应该得是按位与运算,该如何实现这个逻辑呢?

mov cx, ax
jcxz set
mov cx, dx
jcxz set

这个逻辑,显然是两个判断,有一个满足条件则跳转,当然是或关系
与操作应该怎么写?嵌套判断?但是你得知道jcxz指令不会将下一条指令的IP入栈,这条路子暂时不考虑,于是有了另一个思路

mov cx, ax
or cx, 0
or cx, dx
jcxz set

这样利用与零的关系,只有俩同时为0这种情况才会跳转


  • show_str(显示number字段中的字符串)
          show_str:
                  ;X参数cx输入字节
                  ;直接输出number字段中的数值
                  push ax
                  push bx                 ;这里有bl暂存颜色
                  push cx                 ;cl暂存字符,这俩都不能删
                  push es
                  push ds
                  push si
                  push di
    
                  mov ax, number
                  mov ds, ax
    
                  ;mov si, 1500            ;指定位置
                  mov di, 0               ;字符串起始位置
                  mov bl, 00001011B       ;指定颜色,默认
                  mov ax, 0b800h          
                  mov es, ax              ;输出位置
    
                  mov ch, 0
          forward:
                  mov cl, ds:[di]
                  jcxz exit
                  mov es:[si], cl
                  mov es:[si+1], bl
                  add si, 2
                  inc di
                  jmp forward
    
          exit:   
                  pop di
                  pop si
                  pop ds
                  pop es
                  pop cx
                  pop bx
                  pop ax
                  ret
    这个算是比较好实现的,注意参数的暂存,和栈的使用

  • str_store(字符输入到number字段)
          str_store:
                  ;输入;字码,ax低16,dx高16
                  ;输出:number字段
                  push ds
    
                  mov bx, number
                  mov ds, bx
    
                  mov ds:[0], ax
                  mov ds:[2], dx
                  mov byte ptr ds:[4], 0          ;结束符号
    
                  pop ds
                  ret
    为了是结构统一,因为题目中给的年份是4字节的字符形式,故构造一个子程序,将这4个字节输入到number字段

  • 然后就是几个用于显示的子程序

    这几个子程序感觉都像是无参的函数一样,只是执行操作
          show_year:
                  ;输入:年份数据段
                  ;将内容输出到屏幕
                  push ax
                  push cx
                  push dx
                  push si
                  push di
    
                  mov si, 500             ;指定位置
                  mov di, 0
    
                  mov cx, 21
          total_year:
                  mov ax, ds:[di]
                  mov dx, ds:[di+2]
                  call str_store
                  call show_str
                  add di, 4
                  add si, 160
                  loop total_year
                  
                  pop di
                  pop si
                  pop dx
                  pop cx
                  pop ax
                  ret
          show_summ:
                  ;输入总收入字段
                  ;输出数据到屏幕
                  push ax
                  push cx
                  push si
                  push di
    
                  mov si, 530 
                  mov di, 0
                  mov cx, 21
          total_summ:
                  
                  mov ax, ds:[84+di]
                  mov dx, ds:[84+di+2]
                  call dtoc_pro
                  call show_str
    
                  add di, 4
                  ;sub si, 0              ;这里不需要减去偏移量,因为都是从0开始
                  add si, 160
                  loop total_summ
    
                  pop di
                  pop si
                  pop cx
                  pop ax
                  ret
          show_ne:
                  ;输入人数字段
                  ;输出数据到屏幕
                  push ax
                  push cx
                  push si
                  push di
    
                  mov si, 570 
                  mov di, 0
    
                  mov cx, 21
          total_ne:
                  
                  mov ax, ds:[168+di]
                  call dtoc
                  call show_str
    
                  add di, 2
                  ;sub si, 0      ;这里不需要减去偏移量,因为都是从0开始
                  add si, 160
                  loop total_ne
    
                  pop di
                  pop si
                  pop cx
                  pop ax
                  ret
          show_av:
                  ;输入数据段:总收入和人数
                  ;输出平均值(取整)
                  push ax
                  push bx
                  push cx
                  push dx
                  push di
                  push si
    
                  mov si, 610
                  mov bx, 0
                  mov di, 0
                  mov cx, 21
          total_av:
                  push cx
    
                  mov ax, ds:[84+bx]
                  mov dx, ds:[84+bx+2]
                  add bx, 4
                  mov cx, ds:[168+di]
                  add di, 2
                  call div_dw
                  
                  call dtoc_pro
                  call show_str
                  add si, 160
                  pop cx
                  loop total_av
    
                  pop si
                  pop di
                  pop dx
                  pop cx
                  pop bx
                  pop ax
    
                  ret

整体代码附下

assume cs:code, ds:data, ss:stack
data segment
        db '1975','1976','1977','1978','1979','1980','1981','1982','1983','1984','1985'
        db '1986','1987','1988','1989','1990','1991','1992','1993','1994','1995'
 
        dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514,345980
        dd 590827,803530,1183000,1843000,2758000,3753000,4649000,5937000
 
        dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
        dw 11542,14430,15257,17800
data ends

number segment
        db 1024 dup(0)
number ends

stack segment stack                     ;!!!事实证明,后面这个stack加上可以自动赋值sp,不加默认sp从0开始
        db 1024 dup(0)
stack ends

code segment
        start:  

                mov ax, data
                mov ds, ax

                mov ax, stack
                mov ss, ax
                mov sp, 1024            ;养成习惯,数据段及时初始化

                call clear_screen
                call show_year
                call show_summ
                call show_ne
                call show_av
                
                mov ax, 4c00h
                int 21h


;=================Function=================
        ;==================================
        show_year:
                ;输入:年份数据段
                ;将内容输出到屏幕
                push ax
                push cx
                push dx
                push si
                push di

                mov si, 500             ;指定位置
                mov di, 0

                mov cx, 21
        total_year:
                mov ax, ds:[di]
                mov dx, ds:[di+2]
                call str_store
                call show_str
                add di, 4
                add si, 160
                loop total_year
                
                pop di
                pop si
                pop dx
                pop cx
                pop ax
                ret

        ;==================================
        show_summ:
                ;输入总收入字段
                ;输出数据到屏幕
                push ax
                push cx
                push si
                push di

                mov si, 530 
                mov di, 0
                mov cx, 21
        total_summ:
                
                mov ax, ds:[84+di]
                mov dx, ds:[84+di+2]
                call dtoc_pro
                call show_str

                add di, 4
                ;sub si, 0              ;这里不需要减去偏移量,因为都是从0开始
                add si, 160
                loop total_summ

                pop di
                pop si
                pop cx
                pop ax
                ret
        ;==================================
        show_ne:
                ;输入人数字段
                ;输出数据到屏幕
                push ax
                push cx
                push si
                push di

                mov si, 570 
                mov di, 0

                mov cx, 21
        total_ne:
                
                mov ax, ds:[168+di]
                call dtoc
                call show_str

                add di, 2
                ;sub si, 0      ;这里不需要减去偏移量,因为都是从0开始
                add si, 160
                loop total_ne

                pop di
                pop si
                pop cx
                pop ax
                ret
        ;==================================
        show_av:
                ;输入数据段:总收入和人数
                ;输出平均值(取整)
                push ax
                push bx
                push cx
                push dx
                push di
                push si

                mov si, 610
                mov bx, 0
                mov di, 0
                mov cx, 21
        total_av:
                push cx

                mov ax, ds:[84+bx]
                mov dx, ds:[84+bx+2]
                add bx, 4
                mov cx, ds:[168+di]
                add di, 2
                call div_dw
                
                call dtoc_pro
                call show_str
                add si, 160
                pop cx
                loop total_av

                pop si
                pop di
                pop dx
                pop cx
                pop bx
                pop ax

                ret

        ;==========================================================================
        div_dw: 
                ;(输入):ax低16位,dx高16位,cx为16位除数
                ;(输出):dx结果高16位,ax结果低16位,cx为余数
                push bx

                mov bx, ax      ;暂存ax,低位
                mov ax, dx      ;设置被除数 
                mov dx, 0       ;高位置为0
                div cx          ;高16作除法,余数dx并到低16位,这里无需改动,dx也是除法的高位
                push ax         ;暂存高16位除法结果中的商
        
                mov ax, bx
                div cx          ;低16位除法,结果,ax为商,dx为余数

                mov cx, dx      
                pop dx          ;将暂存的高位ax,置于dx

                pop bx
                ret
                
        ;==================================
        dtoc_pro:
                ;输入ax低16,dx高16
                ;内容输出到number字段
                ;注意,检测商为0才跳出
                push bx
                push cx
                push si
                push ds

                mov si, 0               ;记录次数
                mov bx, number
                mov ds, bx              ;目标地址

        in_num_pro: 
                mov cx, 10              ;除数10
                call div_dw             ;返回余数cx
                
                push cx                 ;保存余数
                inc si
                
                push cx                              
                mov cx, ax              ;判断商为0
                or cx, 0
                or cx, dx 
                jcxz in_num_pro_exit    ;商为0,跳转,开始存入number字段
                pop cx
                
                jmp in_num_pro

        in_num_pro_exit:
                pop bx                  ;!!!!!!!!!!!注意多余的push要及时出栈,影响数据排布!!!!
                mov cx, si
                mov si, 0
                mov bx, 0
        set_num_pro:
                pop bx                  ;写入目标地址操作
                add bx, 30h
                mov ds:[si], bl
                inc si             
                loop set_num_pro

                mov byte ptr ds:[si], 0         ;末尾添0,表示字符串结束
                
                pop ds
                pop si
                pop cx
                pop bx
                ret

        ;==================================
        dtoc:
                ;X输入ax,(16位)
                ;结果保存在number字段
                ;注意,检测商为0才跳出!!
                push bx
                push cx
                push dx
                push ds
                push si

                mov bx, number          
                mov ds, bx              ;目标地址

                mov si, 0               ;偏移指针,个数
                mov bx, 10              ;除数
                mov dx, 0               ;32位除法,但这里只有ax参与,dx得置0
                
        in_num: 
                div bx
                push dx
                inc si
                mov cx, ax              ;这里没有loop循环,cx可放心使用
                jcxz in_num_exit
                mov dx, 0
                jmp in_num

        in_num_exit:
                mov cx, si
                mov si, 0
                mov bx, 0
        set_num:
                pop bx
                add bx, 30h
                mov ds:[si], bl
                inc si
                loop set_num
                mov byte ptr ds:[si], 0         ;末尾添0,表示字符串结束

                pop si
                pop ds
                pop dx
                pop cx
                pop bx
                ret 
        ;==================================
        str_store:
                ;输入;字码,ax低16,dx高16
                ;输出:number字段
                push ds

                mov bx, number
                mov ds, bx

                mov ds:[0], ax
                mov ds:[2], dx
                mov byte ptr ds:[4], 0          ;结束符号

                pop ds
                ret
        ;==================================
        show_str:
                ;X参数cx输入字节
                ;直接输出number字段中的数值
                push ax
                push bx                 ;这里有bl暂存颜色
                push cx                 ;cl暂存字符,这俩都不能删
                push es
                push ds
                push si
                push di

                mov ax, number
                mov ds, ax

                ;mov si, 1500            ;指定位置
                mov di, 0               ;字符串起始位置
                mov bl, 00001011B       ;指定颜色,默认
                mov ax, 0b800h          
                mov es, ax              ;输出位置

                mov ch, 0
        forward:
                mov cl, ds:[di]
                jcxz exit
                mov es:[si], cl
                mov es:[si+1], bl
                add si, 2
                inc di
                jmp forward

        exit:   
                pop di
                pop si
                pop ds
                pop es
                pop cx
                pop bx
                pop ax
                ret
        ;==================================
        clear_screen:
                ;无输入
                ;执行清屏操作
                push bx
                push cx
                push es
                push si

                mov bx, 0b800h
                mov es, bx
                mov cx, 4096
                mov si, 0
        swap:   mov byte ptr es:[si], 0
                inc si
                loop swap

                pop si
                pop es
                pop cx
                pop bx
                ret
        ;==================================
code ends
end start