汇编语言-实验(三)

实验目的

  1. 掌握程序设计中的3种基本结构(顺序结构、选择程序、循环程序)。
  2. 熟练使用汇编语言的指令:数据传送类指令、数据运算类指令、逻辑判断类指令与转移指令、循环指令等。

  3. 初步了解系统功能调用的使用方法,尝试使用01H号功能调用进行字符输入的方法及使用02H号功能调用进行字符输出(显示)的方法。

程序说明

  1. 编写十进制到十六进制转换程序。要求从键盘取得一个十进制数,然后把该数以十六进制形式在屏幕上显示出来

  2. 已知从BUF开始存放了10个16进制字数据,编程求出这10个数中的最大数,(将最大数存入MAX字节单元),并将其以10进制数的形式在屏幕上显示出来。(提示:以上两题都要求采用子程序的方法)

  3. 从键盘上输入一行字符,如果这行字符比前一次输入的一行字符长度长,则保存该行字符,然后继续输入另一行字符;如果它比前一次输入的行短,则不保存这行字符。按下‘$’输入结束,最后将最长的一行字符显示出来。(选作)

设计思想

题目一

如下图所示,首先调用子程序getinput获取用户输入的十进制数字,然后调用子程序htoa以十六进制显示用户输入的数据。

dtoh.png

题目二

如下图所示,首先调用子程序getmax获取buf中的最大值,同时存储进max和(ax),最后调用子程序display显示(ax)。

max.png

题目三

​ 首先子程序getstr获取用户输入的字符串,每收到一个字符串之后,根据长度判断是否更新string,然后用户输入一个字符判断输入是否结束;

​ 然后子程序display显示字符串string。

getstr.png

程序代码

题目一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
stack segment			; 数据段
dw 128 dup(?) ; 堆栈大小为128个字
tos label word ; 栈底地址为tos
stack ends

code segment ; 代码段
assume ss:stack,cs:code
main proc far
; 初始化ss、sp寄存器
mov ax,stack
mov ss,ax ; (ss)<-stack
lea sp,tos ; (sp)<-tos,sp存储栈顶地址,当前栈顶即为栈底

; 为返回DOS作准备
push ds
mov ax,0
push ax

; 获取用户输入的十进制数
call getinput

; 调用子程序,在DOS中以十六进制输出(ax)
call htoa

ret
main endp

getinput proc near
mov bx,0 ; bx存储用户输入
mov cl,10 ; 乘法用

input:
; 判断用户输入是否合法
mov ah,1 ; 调用1号功能,获取用户输入,保存至(al)
int 21h ; 调用DOS
sub al,30h ; ASCII码转二进制
cmp al,0 ;
jl exit ; (al)<0,则说明输入结束
cmp al,9
jg exit ; (al)>9,则说明输入结束

; (bx)<-(bx)*10+(al)
push ax ; 暂存(ax)
mov al,bl ; (al)<-(bl)
mul cl ; (ax)<-(al)*(cl)
mov bx,ax ; (bx)<-(ax)==(bx)*10
pop ax ; 恢复(ax),(al)为用户输入的数字
and ax,00ffh ; (ax)高位设为0
add bx,ax ; (bx)<-(bx)+(al)

jmp input ; 继续获取用户输入

exit:
mov ax,bx ; (ax)<-用户输入
ret ; 返回
getinput endp

htoa proc near
cmp ax,15 ; 和15比较
jle blow ; 若(ax)小于等于15,则跳转至blow

; (ax)低4位(大小是一个字)进栈后,右移4位
push ax ; (ax)大于15,暂存ax
push bp ; 暂存bp
mov bp,sp
mov bx,[bp+2] ; (bx)<-(ax),(bp)、(ax)都占一个字,栈底与栈顶相比为高地址
and bx,000fh ; 只保留低4位
mov [bp+2],bx ; 存入栈中
pop bp ; 恢复bp
mov cl,4
shr ax,cl ; 逻辑右移4位
call htoa ; 递归调用子程序htoa
pop ax ; 获取之前存入栈中的低4位
blow:
add al,30h ; (al)转换为ASCII码
cmp al,3ah ;
jl printit ; 若(ax)小于10,则跳转至printit
add al,7h ; (al)大于等于10,加7进位到高位

printit:
mov dl,al ;
mov ah,2 ; 调用2号功能,DOS输出(dl)
int 21h ; 调用DOS

ret
htoa endp

code ends
end main

题目二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
data segment
buf dw 5,9,6,8,7,0,4,3,2,1 ; 定义10个字数据
max dw ? ; 存储最大值
data ends

code segment
assume ds:data,cs:code
main proc far

; 为返回DOS作准备
push ds
mov ax,0
push ax

; 将(ds)设为data
mov ax,data ; (ax) <- data
mov ds,ax ; (ds) <- (ax)

; max记录buf中的最大值
call getmax

; (ax)<-max
mov ax,max

; 十进制输出(ax)
call display

; 返回DOS
ret
main endp

getmax proc near
; 保存寄存器
push ax
push cx
push si

; 数据初始化
mov ax,[buf]
mov max,ax ; 假设第0个元素为最大值
mov cx,9 ; buf还剩9个元素
mov si,2 ; 下标指向第一个元素,因为是字数据,所以为2
compare:
mov ax,buf[si]
cmp max,ax
jge loop1

mov ax,buf[si]
mov max,ax ; 更新最大值
loop1:
add si,2 ; 下标+1
loop compare ; 继续比较

; 恢复寄存器
pop si
pop cx
pop ax

; 返回
ret
getmax endp

display proc near ; 将(ax)以十进制形式输出

; 保存寄存器
push ax
push bx
push cx
push dx
push si

; 初始化
mov si,0 ; 数字的位数
mov bl,10 ; 数字之后除以bx,即10


; 数字每位倒序压栈
prepare:
div bl ; 被除数默认为(ax),除以10。
add ah,30h ; (ah)为默认的余数寄存器,转换为ASCII码,字符0的ASCII码为48,即30h
push ax ; 存储最后一位
and ax,00ffh; (ah)<-0
mov cx,ax ; al为默认的商寄存器。ax=ah+al。当商为0时则跳出循环
inc si ; 更新数字位数
inc cx ; 如果为0,加一之后为1。loop判断前会将cx减一。
loop prepare;

; 弹栈,并用十进制显示
mov cx,si ; si为数字位数,即循环运行次数
show:
pop ax ; 获取要显示的数据
mov dl,ah ; 要显示的数据放在dl里
mov ah,2 ; 2号功能
int 21h ; 调用DOS
loop show

; 恢复寄存器
pop si
pop dx
pop cx
pop bx
pop ax

; 返回
ret

display endp ; display子程序结束

code ends
end main

题目三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
data segment
string db 0 ; 存放字符的个数
db 80 dup(0),0dh,0ah,'$' ; 存放前一次输入的字符串,兼作显示缓冲区。0ah换行、0dh回车(归位)
buffer db 80 ; 输入字符串的缓冲区,最多输入80个字符
db 0 ; 存放字符的个数
db 80 dup(0) ; 存放当前输入的字符串,20h为空格
data ends

code segment
assume cs:code,ds:data,es:data
main proc far

; 为返回dos作准备
push ds
mov ax,0
push ax

; (ds)<-(ax) (es)<-(ax)
mov ax,data
mov ds,ax
mov es,ax

; 调用子程序,获取用户输入的串,判断是否更新,最终输出
call getstr

; 显示最终串
call display

; 程序返回
ret
main endp

getstr proc near

; 保存寄存器
push ax
push cx
push dx
push di
push si

; 获取用户输入的字符串存入buffer
input:
lea dx,buffer ; dx存缓冲区首址
mov ah,0ah ; DOS调用0A号功能,输入字符到缓冲区DS:DX
int 21h ; DOS调用

; DOS光标换行(处理多组输入,因为按下Enter仅能使光标回车)
mov ah,2
mov dl,0ah
int 21h

; 比较字符串长度
lea si,buffer+1 ; buffer+1是当前串长度的地址
lea di,string ; (string)存入di,(string+2)存入es,源操作数只能用存储器寻址方式,目的寄存器不允许使用段寄存器
mov al,[si] ; 当前串长度存入al
cmp al,[di] ; 与之前的串长度进行比较
jbe next ; 如果当前串更短,跳转到next,接收下一个字符串

; 更新之前串string
mov cx,80+1 ; 当前串比之前串长,更新串
cld ; 正向处理字符串
rep movsb ; 重复串操作,di为目的串,si为源串

; 处理$失效问题
mov ah,0
mov si,ax
mov [string+si+1],'$'

; 获取下一个串
next:
mov ah,1 ; 获取用户输入
int 21h ; DOS调用
cmp al,'$' ; 是结束符吗?
jne input ; 不是则继续输入

; 恢复寄存器
pop si
pop di
pop dx
pop cx
pop ax

; 子程序返回
ret
getstr endp

display proc near
; 保护寄存器
push dx
push ax

; DOS光标换行,处理用$结束输入后没有换行的问题
mov ah,2
mov dl,0ah
int 21h

; 显示最终串
lea dx,string+1 ; 串地址存入dx
mov ah, 9 ; dos调用9号功能,显示串
int 21h ; 调用DOS

; 恢复寄存器
pop ax
pop dx

ret
display endp
code ends
end main

结果分析

前两题略

题目三

遇到的问题及解决方法如下:

  1. 获取用户输入的字符串时,按下Enter之后,光标回车,由于题目是多个输入(用户只输入一个字符串时,也要用$作为一次输入,所以也算多个输入),所以要增加换行操作。
  2. 输出结果时string后边的

  3. 出现吞字问题:除了第一次输入,之后的输入第一个字符都会被当做判断输入结束的字符而不是字符串的内容…所以会吞字。

作者:@臭咸鱼

转载请注明出处:https://chouxianyu.github.io

欢迎讨论和交流!