;;------------------------------------------------------------------------------------------
;; 王爽汇编课程设计1(第211页)
;; 感谢王爽老师的这本《汇编语言》,让我受益匪浅,尤其是代码段,数据段,栈段的讲解,
;; 太经典了,看了之后已经对这本书爱不释手,以前看过单片机的的汇编,8086的汇编刚学了两
;; 周,下面是我对课程设计1的实现。
;; 求助:
;; 1)我觉得我写的代码很不规范,希望高手在编写规范的和高质量的汇编代码方面给我多提出意见。
;; 2)我不知道如何将汇编的多个子模块分别放在多个*.asm文件里,然后像C语言一样编译连接相互
;; 调用的各个模块文件,希望高手能给出帮助,谢谢
;; 作者:wangkaichao2
;;-----------------------------------------------------------------------------------------
assume cs:code
data segment
db '1975', '1976', '1977', '1978', '1979', '1980', '1981', '1982', '1983'
db '1984', '1985', '1986', '1987', '1988', '1989', '1990', '1991', '1992'
db '1993', '1994', '1995'
;0 + 21 * 4 = 84
dd 16, 22, 382, 1356, 2390, 8000, 16000, 24486, 50065, 97479, 140417, 197514
dd 345980, 590827, 803530, 1183000, 1843000, 2759000, 3753000, 4649000, 5937000
;84 + 21 * 4 = 168
dw 3, 7, 9, 13, 28, 38, 130, 220, 476, 778, 1001, 1442, 2258, 2793, 4037, 5735, 8226
dw 11542, 14430, 15257, 17800
;168 + 2 * 21 = 210
data ends
table segment
db 80 dup (0)
table ends
code segment
;;------------------------------------------------------------------------------------
;; 王爽汇编课程设计1(第211页)
;; MAIN
;;------------------------------------------------------------------------------------
start:
mov ax, data
mov es, ax
mov di, 0
mov bx, 0
mov ax, table
mov ds, ax ;指定table的段地址
mov si, 0
mov dh, 3 ;行号
mov dl, 0 ;列号
mov cx, 21
next_line:
push cx ;循环体中用到cl
call fill_table
inc dh ;行号加1,显示下一行
mov cl, 2 ;绿色字
call show_str ;入参(dh),(dl),(cl),ds:si
add di, 4 ;如果把data段看作结构体数组,(di)就是结构体数组中“年份”和“总收入”下标
add bx, 2 ;(bx)是结构体数组中“公司雇员”的下标
pop cx
loop next_line
mov ax, 4c00H
int 21H
;;------------------------------------------------------------------------
;;名称:show_str
;;功能:在指定位置,用指定的颜色,显示一个用0结束的字符串
;;入参:(dh)=行号(0~24),(dl)=列号(0~79),(cl)=颜色,
;; ds:si指向字符串的首地址
;;返回值:无
;;------------------------------------------------------------------------
show_str: ;dh * A0H + dl定位显存偏移起始地址
push dx
push ds
push si
push ax
push bx ;显存偏移地址
push es ;显存段地址
push cx ;保存ch中的未知数据
mov ax, 0b800H
mov es, ax
mov ax, 0a0H ;一行a0H个字节
mul dh ;计算第(dh)行的起始偏移地址
mov dh, 0 ;清零,只保留dl中的列数
add ax, dx ;加上列地址,不行的话改为mov dh, 0;add ax, dx
add ax, dx ;一列占两个字节
mov bx, ax ;es:bx = 字符串在显存中的起始地址
mov ah, cl ;ah存储颜色
mov ch, 0
showChar: ;将字符串循环填充到显存
mov cl, [si]
jcxz toNull
mov al, cl ;al存储ASCII码
mov es:[bx], ax ;将ASCII码和属性送入显存
add bx, 2
inc si
jmp short showChar
toNull:
pop cx
pop es
pop bx
pop ax
pop si
pop ds
pop dx
ret ;;show_str end
;;-------------------------------------------------------------------------------
;; 名称:dtoc
;; 功能:将dword型数据转变为表示十进制数的字符串,字符串以0为结尾符。
;; 参数:(dx)=dword型高16位
;; (ax)=dword型低16位
;; ds:si指向字符串首地址
;; 返回:无
;; 应用举例:将12666以十进制形式在屏幕的8行3列,用绿色显示
;; 该子模块调用了show_str, divdw子程序段
;;-------------------------------------------------------------------------------
dtoc:
push dx
push ax
push ds
push cx
push si
next_div:
mov cx, 10
call divdw ;商(dx)(ax),余数(cx)
add cx, 30H
mov [si], cl ;ASCII占一个字节
inc si
;;判断(ax)是否等于0
mov cx, ax
add cx, 1 ;cx先增1,改成 inc cx ?
loop next_div ;cx先减1,if(cx != 0),商不为0
;;在(ax)等于0的前提下,判断(dx)是否等于0
mov cx, dx
jcxz div_end ;if(dx==0),商为0,循环结束
loop next_div
div_end:
mov byte ptr [si], 0 ;字符串0结束符
pop si ;ds:si=字符串起始地址
call str_reserve ;颠倒字符串
pop cx
pop ds
pop ax
pop dx
ret ;;dtoc子程序结束
;;--------------------------------------------------------------------------------------
;; 名称:divdw
;; 功能:进行不会产生溢出的除法运算,被除数dword型,除数word型
;; 入参:(dx) = 被除数高16位,(ax)= 被除数第16位,(cx) = 除数
;; 返回值:(dx) = 商高16位,(ax) = 商第16位,(cx) = 余数
;; 余数为word型,结果为dword型
;; int():描述性运算符,取商,比如,int(38/10)=3
;; rem():描述性运算符,取余,比如,rem(38/10)=8
;; X:被除数,N:除数
;; 公式:X/N = int(H/N)*10000H + [rem(H/N)*10000H + L]/N
;;--------------------------------------------------------------------------------------
divdw:
push bx
push ax ;被除数低16位入栈
mov ax, dx ;处理高16被除数
mov dx, 0
div cx ;int(H/N)*10000H
mov bx, ax ;bx暂存高16位除后的商
pop ax ;获取被除数低16位
div cx ;[rem(H/N)*10000H + L]/N
mov cx, dx ;cx存放余数
mov dx, bx
pop bx
ret ;;end divdw
;;-------------------------------------------------------------------------------------
;; 名称:str_reserve
;; 功能:将数据段中的以0结尾字符串逆序排列
;; 参数:ds:si = 字符串首地址
;; 返回值:(cx) = 字符串长度
;; 该子模块调用了get_str_length
;;-------------------------------------------------------------------------------------
str_reserve:
push ds
push si ;字符串前后交换时,(si)从第一个字符开始向中间移动
push di ;字符串前后交换时,(di)从最后一个字符开始向中间移动
push ax ;除法操作
call get_str_length ;字符串长度在(cx)中
push cx ;将字符长度入栈
mov di, si
add di, cx
dec di ;(di)=最后一个字符的下标
mov ax, cx
mov cl, 2
div cl ;该8位除法只针对长度小于512的字符串,>=512产生除法溢出
mov ah, 0
add ax, si ;;(ax)=字符中间下标,
next_char_reserve:
mov cx, ax ;(ax)是字符串中间下标
sub cx, si ;if(cx == 0),字符串逆序完成
jcxz str_reserve_end
;;交换前后字符
mov cl, [si]
mov ch, [di]
mov [di], cl
mov [si], ch
dec di
inc si
jmp next_char_reserve
str_reserve_end:
pop cx ;字符创长度出栈
pop ax
pop di
pop si
pop ds
ret ;end str_reserve
;;-------------------------------------------------------------------------------------
;; 名称:get_str_length
;; 功能:获取一个以0结束的字符串的长度
;; 入参:ds:si = 字符串首地址
;; 返回值:(cx) = 字符串长度
;;-------------------------------------------------------------------------------------
get_str_length: ;返回值(cx)
push ds
push si
mov cx, 0
next_char:
mov cl, [si]
jcxz str_end
inc si
jmp short next_char
str_end:
mov cx, si
pop si
pop ds
sub cx, si ;(cx)-(si)的值就是字符串长度
ret ;end get_str_length
;;----------------------------------------------------------------------------------
;; 名称:fill_table
;; 功能:将data段(结构体数组)中的所有字段,转换成以零结束的字符串,存储到table段中,
;; 格式如下:
;; 0123456789012345678901234567890123456789|
;; 1975 16 3 5 |结束符
;; 入参:ds:si = table段首地址,es:di = ,es:bx
;; 补充说明: es:[di].0 = data中某一年份(4个byte)首地址
;; es:[di].84 = data中某一年份总收入(dword)首地址
;; es:[bx].168 = data中某一年份公司雇员(dw)字段首地址
;; 返回值:无
;; 该子模块调用了fill_blank,get_str_length,dtoc
;;----------------------------------------------------------------------------------
fill_table:
push es
push di ;;(es):(di) = data中的数据
push bx ;;(es):(bx) = data中的数据
push ds
push si ;;(ds):(si) = table中的数据
push cx ;;控制各种循环及jcxp指令等
push ax
push dx ;;除法操作入参
;;填充年份
mov cx, 4
push di
s0:
mov al, es:[di]
mov [si], al
inc di
inc si
loop s0
pop di
;;填充空格
mov cx, 10 ;;cx绝对下标
call fill_blank ;入参(cx), ds:si
mov si, cx ;ds:si = 空格后(收入)字符串起始地址
;;填充公司收入 (si)=10H
mov ax, es:[di+0].84
mov dx, es:[di+2].84
call dtoc
call get_str_length ;(cx)=收入字符串长度
add si, cx ;ds:si=收入字符串0结束符地址
;;填充空格
mov cx, 20
call fill_blank
mov si, cx
s4: ;;填充雇员数 2字节
mov ax, es:168[bx] ;入参
mov dx, 0 ;入参
call dtoc
call get_str_length
add si, cx
;;填充空格,上面入栈的(di)没有出栈是因为计算人均收入时还要用到现在减半的(di)
mov cx, 30
call fill_blank
mov si, cx
;;填充人均收入
mov cx, es:168[bx] ;入参,这里的(di)是减半后的(di)
mov ax, es:84[di] ;入参
mov dx, es:84[di+2] ;入参
call divdw
call dtoc
call get_str_length
add si, cx
;;填充空格
mov cx, 40
call fill_blank
mov si, cx
;;填充字符串结束符
mov byte ptr [si], 0
;;end
pop dx
pop ax
pop cx
pop si
pop ds
pop bx
pop di
pop es
ret ;;end fill table
;;--------------------------------------------------------------------------------------
;;名称:fill_blank
;;功能:将指定的一段偏移内存填充为空格字符
;;入参:ds:si = 填充空格起始下标,(cx) = 填充空格截止下标,该字节不会被空格填充
;;返回值:无
;;--------------------------------------------------------------------------------------
fill_blank: ;;入参(cx)=填充空格截止下标,ds:si = 填充空格起始下标
push ds
push si
push bx
fill_next_blank:
push cx ;填充空格截止下标入栈
sub cx, si
jcxz fill_blank_end ;(cx)==0时,栈顶为(cx)
mov bl, 20H
mov [si], bl
inc si
pop cx ;填充空格截止下标出栈
jmp fill_next_blank
fill_blank_end:
pop cx
pop bx
pop si
pop ds
ret ;;end fill_blank
;;-------------------------------------------------------------------------------------
code ends
end start