Skip to content

Latest commit

 

History

History
262 lines (188 loc) · 6.87 KB

File metadata and controls

262 lines (188 loc) · 6.87 KB

第23节 寄存器冲突的问题

❤️💕💕汇编语言目前仍在发挥着不可替代的作用,在效率上无可替代,在底层,学习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

现在,我们来看一下子程序的应用。

  1. 将 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
  1. 将 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即用于循环,也用于读取数据 – 丢失)。

那么如何避免这种冲突呢? 粗略地看,可以有以下两个方案。

  1. 在编写调用子程序的程序时,注意看看子程序中有没有用到会产生冲突的寄存器,如果有,调用者使用别的寄存器。

  2. 在编写子程序时,不要使用会产生冲突的寄存器。

我们来分析一下上面两个方案的可行性:

  1. 这将给调用子程序的程序的编写造成很大的麻烦,因为必须要小心检查所调用的子程序中是否有将产生冲突的寄存器。 比如说,在上面的例子中,我们在编写主程序的循环时就得检查子程序中是否用到了 cx,因为如果子程序中用到了这个寄存器就会出现问题。 如果采用这种方式来解决问题冲突的话,那么在主程序的循环中,就不能使用 cx 寄存器,因为子程序中已经用到。

  2. 这个方案不可能实现,因为编写子程序时无法知道将来的调用情况。

可见,我们上面所设想的两个方案都不可行,我们希望:

  1. 编写调用子程序的程序时不必关心子程序到底使用了哪些寄存器;

  2. 编写子程序时不必关心调用者使用了哪些寄存器;

  3. 不会发生寄存器冲突。

解决冲突

解决这个问题的简捷方法:在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在子程序返回前再恢复。 可以用栈来保存寄存器的内容。

  1. 编写调用子程序的程序的时候不必关心子程序使用的寄存器
  2. 编写子程序的时候不必关心调用者使用了哪些寄存器
  3. 不会发生寄存器冲突

以后,我们编写子程序的标准框架如下:

子程序开始:子程序使用的寄存器入栈
            子程序内容
            子程序中使用的寄存器出栈
            返回(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

END 链接