❤️💕💕汇编语言目前仍在发挥着不可替代的作用,在效率上无可替代,在底层,学习linux内核,计算机外围设备和驱动,都离不开汇编。Myblog:http://nsddd。top
[TOC]
设计一个子程序,功能:将一个全是字母,以 0 结尾的字符串,转化为大写。
程序要处理的字符串以 0 作为结尾符,这个字符串可以如下定义:
db 'conversation', 0
应用这个子程序,字符串的内存后面一定要有一个 0,标记字符串的结束。 子程序可以依次读取每个字符进行检测,如果不是为 0,就进行大写的转化;如果是 0,就结束处理。由于可通过检测 0 而知道是否处于已经处理完整个字符串,所以子程序可以不需要字符串的长度作为参数。 可以用 jcxz 来检测 0。
; 说明: 将一个全是字母,以0结尾的字符串,转化为大写
; 参数:ds:si指向字符串的首地址
; 结果:没有返回值
captial:mov cl, [si]
mov ch, 0
jcxz ok ; 如果(cx) = 0, 结束; 如果不是0,处理
and byte ptr [si], 11011111b ; 将ds:si所指单元的字母转化为大写
inc si ; ds:si指向下一个单元
ok: ret
现在,我们来看一下子程序的应用。
- 将 data 段中字符串转化为大写:
assume cs:code
data segment
db 'conversation', 0
data ends
代码段中的相关程序段如下:
mov ax, data ;设置字符串的起始地址,并调用子程序
mov ds, ax
mov si, 0
call captial
完整的程序如下:
assume cs:code
data segment
db 'conversation', 0
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
call captial
mov ax, 4c00h
int 21h
captial:mov cl, byte ptr [si]
mov ch, 0
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short captial
ok: ret
code ends
end start
- 将 data 段中的字符串全部转化为大写:
assume cs:code
data segment
db 'word', 0
db 'unix', 0
db 'wind', 0
db 'good', 0
data ends
可以看到,所有字符串的长度都是 5(算上结尾符 0),使用循环,重复调用子程序 captial,完成对 4 个字符串的处理。 完整的程序如下:
assume cs:code
data segment
db 'word', 0
db 'unix', 0
db 'wind', 0
db 'good', 0
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov cx, 4
s: call captial
add si, 5
loop s
mov ax, 4c00h
int 21h
captial:mov cl, byte ptr [si]
mov ch, 0
jcxz ok ;cs为0 的话跳转,不为0 的话继续 ~
and byte ptr [si], 11011111b
inc si
jmp short captial
ok: ret
code ends
end start
思考:上面的程序思想上完全正确,但在细节上却有些错误,把错误找出来。
分析:
问题在于 cx 的使用,主程序要使用 cx 记录循环次数,可是子程序中也使用了 cx,在执行子程序时,cx 中保存的循环计数值被改变,使得主程序的循环出错。
从上面的问题中,实际上引出了一个一般化的问题:子程序中使用的寄存器,很可能在主程序中也要使用,造成了寄存器使用上的冲突(cx即用于循环,也用于读取数据 – 丢失)。
那么如何避免这种冲突呢? 粗略地看,可以有以下两个方案。
-
在编写调用子程序的程序时,注意看看子程序中有没有用到会产生冲突的寄存器,如果有,调用者使用别的寄存器。
-
在编写子程序时,不要使用会产生冲突的寄存器。
我们来分析一下上面两个方案的可行性:
这将给调用子程序的程序的编写造成很大的麻烦,因为必须要小心检查所调用的子程序中是否有将产生冲突的寄存器。 比如说,在上面的例子中,我们在编写主程序的循环时就得检查子程序中是否用到了 cx,因为如果子程序中用到了这个寄存器就会出现问题。 如果采用这种方式来解决问题冲突的话,那么在主程序的循环中,就不能使用 cx 寄存器,因为子程序中已经用到。
这个方案不可能实现,因为编写子程序时无法知道将来的调用情况。
可见,我们上面所设想的两个方案都不可行,我们希望:
编写调用子程序的程序时不必关心子程序到底使用了哪些寄存器;
编写子程序时不必关心调用者使用了哪些寄存器;
不会发生寄存器冲突。
解决这个问题的简捷方法:在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在子程序返回前再恢复。 可以用栈来保存寄存器的内容。
- 编写调用子程序的程序的时候不必关心子程序使用的寄存器
- 编写子程序的时候不必关心调用者使用了哪些寄存器
- 不会发生寄存器冲突
以后,我们编写子程序的标准框架如下:
子程序开始:子程序使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回(ret, retf)
我们改进一下子程序 captial 的设计:
change: mov cl, byte ptr [si]
mov ch, 0
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
完整程序如下:
assume cs:code
data segment
db 'word', 0
db 'unix', 0
db 'wind', 0
db 'good', 0
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov cx, 4 ;循环四次
s: call captial
add si, 5
loop s
mov ax, 4c00h
int 21h
captial:push cx
push si
;下面需要用到si和ch,我们就一开始入栈cx和si
change: mov cl, byte ptr [si]
mov ch, 0
jcxz ok
and byte ptr [si], 11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
code ends
end start