7.38 王爽汇编语言(三十五)

课程设计 1

解析: 首先将数据放到数据段再说,要显示出来的话得放入显示缓冲区,可以看到是21行数据,差不多 可以搞,对了是以字符的形式,所以需要对内存中的数据进行转化。

好家伙,自己写不出来,看看鱼c论坛帖子把哈哈哈哈!

# 参考链接
1.https://fishc.com.cn/thread-157074-1-1.html   # 360多行代码,整整比下一个链接多了一半。。
2.https://fishc.com.cn/thread-155084-1-1.html   # 模块化编程思想 178行代码

看了帖子,我坚信,这绝对会成为迄今为止写过的最长的汇编程序

复习一下DOS BOX debug.exe的各种指令: 参考链接:https://blog.csdn.net/ocean35/article/details/88899283 其中查看内存单元的 d 命令很重要:

# 格式如下
(1)d 段地址:偏移地址
如 d ds:0 即显示 ds:0 地址往后 128 个内存单元的内容

(2)d 段地址:偏移地址1 偏移地址2 
如 d ds:0 90 即显示 ds:0 地址到 ds:90H 所有内存单元的内容

(3)d 段地址:偏移地址 Lm
如 d ds:0 L2 即显示 ds:0 地址往后 2 个内存单元的内容
assume cs:codesg, ds:datasg, ss:stacksg

datasg segment

    ;; 参数段           偏移地址范围:0000H ~ 000FH
    db 10H dup(0)       
    ;; 字符串倒序暂存缓冲区             偏移地址范围: 0010H ~ 001FH
    db 10H dup(0)
    ;; 21 份日期数据                偏移地址范围:0020H ~ 004FH
    dw 1975, 1976, 1977, 1978,      1979, 1980, 1981, 1982
    dw 1983, 1984, 1985, 1986,      1987, 1988, 1989, 1990
    dw 1991, 1992, 1993, 1994,      1995,    0,    0,    0

datasg ends

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

stacksg segment

    db 20H dup(0)       ;; 32个字节用作栈段

stacksg ends

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

codesg segment

main:
    mov ax, datasg
    mov ds, ax          ;; 初始化数据段段地址
    mov ax, stacksg
    mov ss, ax          
    mov sp, 20H         ;; 初始化栈顶指针
    mov ax, 0B800H      
    mov es, ax          ;; 初始化显存段段地址

parameter_for_date:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    mov word ptr ds:[0000H], 020H           ;; 年份数据偏移地址
    mov word ptr ds:[0002H], 2-2            ;; 数据的字节大小
    mov word ptr ds:[0004H], (1-1)*160       ;; 最开始在第几行进行显示,这里为第 1 行,所以其实行偏移为 0
    add word ptr ds:[0004H], (4-1)*2       ;; 最开始在第几列进行显示,这里为第 4 列,所以其实列偏移为 6,这里使用 add,即算出来在显存中的偏移地址
    mov word ptr ds:[0006H], 160           ;; 字符间隔,因为 80 个字符占位(其中包含了字符ASCII占 1 个字节,颜色占 1 个字节)
    mov byte ptr ds:[0008H], 02H           ;; 字符颜色,02H 代表黑底绿色
    mov word ptr ds:[0009H], 0             ;; 即保存写入字符串倒序暂存缓冲区的位数,方便后续 loop 显示正常顺序的字符串
    mov di, 10H                            ;; 字符串倒序暂存缓冲区的起始偏移地址

call dtoc_printf                           ;; 显示年份
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    mov ax, 4c00H    
    int 21H
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

dtoc_printf:
    mov si, ds:[0000H]      ;; 待显示数据所在的内存起始偏移地址
    mov bp, ds:[0004H]      ;; 显示起始显存偏移地址
    mov bx, 10              ;; ASCII 转换的除数
    mov cx, ds:[0002H]      ;; cx 作为字节大小判定的标准,如果待显示数据为 1 个字,则 cx 为 0;如果待显示数据为 双字类型,则 cx 为 2;
    jcxz dtoc_printf_byte2  ;; 如果待显示数据的类型为 1 个字,那么跳转到 dtoc_printf_byte2 进行处理
    jmp dtoc_printf_byte4   ;; 如果待显示数据的类型为 双字,那么跳转到 dtoc_printf_byte4 进行处理

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

dtoc_printf_byte2:         
    mov dx, 0                ;; 对于 16 位除法(即除数为 16 位),其被除数需要为 32 位;但是因为待显示数据的类型为单字,不需要占用被除数的高位,所以被除数高位,即 DX 置为 0
    mov ax, ds:[si]          ;; 单字数据放到被除数低位,即 AX 寄存器中
    jmp dtoc_printf_s        ;; 跳转至求余运算


dtoc_printf_s:               ;; 循环求余转 ASCII,写入字符串倒序暂存缓冲区中
    push bp                  ;; bp 原本用来保存起始显存偏移地址,所以需要入栈保存
    call divdw               ;; 进行双字除法运算
    pop bp                   ;; 恢复 bp
    mov ch, ds:[008h]        ;; 字符颜色属性写入ch,作者是add,我觉得一样,因为余数不可能大于 10,那么 ch 就不会保存数
    add cl, 30h              ;; 余数 + 30h 转换为对应字符的 ASCII 编码值
    mov ds:[di], cx          ;; 字符写入显示暂存区
    inc word ptr ds:[009h]   ;; 字符位数自加1
    add di, 2                ;; 指向下一个显示暂存区
    mov cx, ax               ;; 判断商是否为零,如果为零则说明 ASCII 除法结束,则可以打印了
    jcxz dtoc_printf_out
    jmp dtoc_printf_s        ;; 否则继续运算

dtoc_printf_out:
    sub di, 2                ;; 显示暂存区指针修正
    mov cx, ds:[009h]        ;; 字符位数写入cx
    push cx                  ;; 入栈保存 cx

dtoc_printf_outs:
    mov ax, ds:[di]          ;; 字符写入ax
    mov es:[bp], ax          ;; ax写入显存
    sub di, 2                ;; 指向下一位字符
    add bp, 2                ;; 指向下一位显存
    loop dtoc_printf_outs    ;; 循环将字符倒序暂存区的字符转移到显存中
    mov word ptr ds:[009h], 0   ;; 输出完毕,字符位数清零
    add di, 2                   ;; 字符倒序暂存区指针修正
    pop cx                      ;; 原字符位数出栈
    sub bp, cx                  ;; 通过字符位数修正显存地址
    sub bp, cx                  ;; 通过字符位数修正显存地址
    add bp, ds:[006h]           ;; 字符间隔累加到显存地址

divdw:                       ;; 双字不溢出除法:结果是 cx 保存最终的余数,ax 保存结果低位商,dx 保存结果高位商,公式:X/N = int(H/N)*65536 + [rem(H/N)*65536 + L]/N
    push ax                  ;; 低位入栈保存
    mov ax, dx               ;; 高位写入低位
    xor dx, dx               ;; 高位数据清零
    div bx                   ;; int(H/N)
    mov bp, ax               ;; 高位商暂存bp
    pop ax                   ;; 弹出之前保存的被除数低位到 AX
    div bx                   ;; [rem(H/N)*65536 + L]/N,在运算前,dx已经保存了高位运算的余数
    mov cx, dx               ;; 最终余数保存到 cx
    mov dx, bp               ;; 把结果高位商保存到 dx,ax此时保存了低位商
    ret

codesg ends

end main

总结

有段时间没更新了,一直卡在这道题,还正在做,一直在看鱼c的代码,明天继续分析~。

Last Updated:
Contributors: Hunter-0x07