当前位置: 首页 > news >正文

从入门到精通汇编语言 第五章(流程转移与子程序)

参考教程:通俗易懂的汇编语言(王爽老师的书)_哔哩哔哩_bilibili

一、“转移”概述

1、转移的概念

(1)般情况下指令是顺序地逐条执行的,而在实际中,常需要改变程序的执行流程,这就需要使用到转移指令。

(2)转移指令可以控制CPU执行内存中某处代码的指令,它通过修改IP寄存器,或同时修改CS寄存器和IP寄存器实现

2、转移的分类

(1)按转移行为分类:

①段内转移:只修改IP寄存器,如“JMP AX”。

②段间转移:同时修改CS寄存器和IP寄存器,如“JMP 1000:0”。

(2)根据指令对IP寄存器修改的范围(即在原内容基础上的修改幅度)不同分类(非全):

①段内短转移:IP修改范围为-128~127。

②段内近转移:IP修改范围为-32768~32767。

(3)按转移指令分类:

①无条件转移指令。

②条件转移指令。

③循环指令。

④中断。

⑤过程调用。

3、取得标号的偏移地址及段地址

(1)使用操作符offset可取得标号对应内存在其段中的偏移地址使用操作符seg可取得标号对应内存在其段的段地址

(2)举例:程序在运行中将s处的一条指令复制到s0处。

assume cs:codesg

codesg segment

        s:     mov ax, bx

                mov si, offset s  ;将标号s对应内存在其段中的偏移地址存入SI

                mov di, offset s0  ;将标号s0对应内存在其段中的偏移地址存入DI

                mov ax, cs:[si]   ;将s处的指令(占两个字节)内容送入AX

                mov cs:[di], ax  ;将AX中的内容送入s0对应的内存

        s0:   nop     ;空语句,占一个字节

                nop     ;空语句,占一个字节

codesg ends

end start

二、JMP指令

1、概述

(1)JMP指令可实现无条件转移,可以只修改IP寄存器,也可以同时修改CS寄存器和IP寄存器

(2)JMP指令要给出两种信息的其中一个:

①转移的距离(转移有向前和向后之分,转移距离分别对应正数和负数,用补码表示)。

②转移的目的地址。

2、依据位移进行转移

(1)依据位移进行转移可分为段内短转移和段内近转移。

①段内短转移:IP修改范围为-128~127,指令格式为“JMP short <标号>”。

②段内近转移:IP修改范围为-32768~32767,指令格式为“JMP near ptr <标号>”。

(2)jmp short的机器指令中,包含的是跳转到指令的相对位置,而不是转移的目标地址(简而言之,就是转移距离,范围为-128~127,超出范围会报错),如下所示。

assume cs:codesg

codesg segment

        start:  mov ax, 0

                  jmp short s   ;程序员写入标号即可,8位转移距离由编译器计算

                  add ax, 1

        s:       nop     ;空语句,占一个字节

codesg ends

end start

①当程序运行至“jmp short s”指令时,(IP) = 0003H,CS:IP指向EB 05(“jmp short s”指令的机器码)。

②CPU读取指令码EB 05进入指令缓冲器。

③(IP) = (IP) + 2 = 0005H(2为指令码EB 05的长度),此时CS:IP指向指令“add ax, 1”。

④CPU执行指令缓冲器中的指令EB 05。

⑤指令EB 05执行后,(IP) = (IP) + 05 = 000AH,CS:IP指向“nop”。

(3)jmp near ptr的机器指令中,包含的是跳转到指令的相对位置,而不是转移的目标地址(简而言之,就是转移距离,范围为-32768~32767,超出范围会报错),如下所示。

assume cs:codesg

codesg segment

        start:  mov ax, 0

                  jmp near ptr s   ;程序员写入标号即可,16位转移距离由编译器计算

                  db 128 dup (0)

        s:       nop     ;空语句,占一个字节

codesg ends

end start

①当程序运行至“jmp near ptr s”指令时,(IP) = 0003H,CS:IP指向E9 00 01(“jmp near ptr s”指令的机器码)。

②CPU读取指令码E9 00 01进入指令缓冲器。

③(IP) = (IP) + 3 = 0006H(3为指令码E9 00 01的长度),此时CS:IP指向128个字节数据的第一个字节。

④CPU执行指令缓冲器中的指令E9 00 01。

⑤指令E9 00 01执行后,(IP) = (IP) + 100H = 0106H,CS:IP指向“nop”。

3、依据目的地址进行转移

(1)远转移的指令格式为“JMP far ptr <标号>”,far ptr指明了跳转到的目的地址,即包含了标号的段地址CS和偏移地址IP

(2)举例:

assume cs:codesg

codesg segment

        start:  mov ax, 0

                  mov bx, 0

                  jmp far ptr s   ;程序员写入标号即可,目的地址由编译器计算

                  db 128 dup (0)

        s:       nop     ;空语句,占一个字节

codesg ends

end start

①当程序运行至“jmp far ptr s”指令时,(IP) = 0006H,CS:IP指向EA 0B 01 6A 07(“jmp far ptr s”指令的机器码)。

②CPU读取指令码EA 0B 01 6A 07进入指令缓冲器。

③(IP) = (IP) + 5 = 000BH(5为指令码E9 00 01的长度),此时CS:IP指向128个字节数据的第一个字节。

④CPU执行指令缓冲器中的指令EA 0B 01 6A 07。

⑤指令EA 0B 01 6A 07执行后,(IP) = 010B,(CS) = 076A。

4、转移地址在寄存器或内存中的JMP指令

(1)转移地址在寄存器中的JMP指令:

①格式:JMP <16位寄存器>。(如“jmp ax”)

②作用:寄存器中存放着一个字,它是转移的目的偏移地址

(2)转移地址在内存中的JMP指令:

①段内转移:

[1]格式:JMP word ptr <内存单元地址>或JMP word ptr <段寄存器>:<内存单元地址>。

[2]作用:从内存单元地址处开始存放着一个字,它是转移的目的偏移地址

[3]举例:

②段间转移:

[1]格式:JMP dword ptr <内存单元地址>或JMP dword ptr <段寄存器>:<内存单元地址>。

[2]作用:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,低地址处是转移的目的偏移地址

[3]举例:

三、一些其它的转移指令

1、JCXZ指令

(1)指令格式:JCXZ <标号>。

(2)指令功能:如果(cx)=0,执行此条指令后将程序转移至标号处,否则执行此条指令后程序按顺序继续执行指令

(3)此条指令改变的是IP寄存器中的值,修改范围为-128~127,底层原理同“JMP short <标号>”,此处不再赘述

2、LOOP指令

(1)指令格式:LOOP <标号>。

(2)指令功能:如果(cx)=0,执行此条指令后程序按顺序继续执行指令,否则执行此条指令后将程序转移至标号处

(3)此条指令改变的是IP寄存器中的值,修改范围为-128~127,底层原理同“JMP short <标号>”,此处不再赘述

四、CALL指令和RET指令

1、CALL指令

(1)根据字面意思,CALL指令用于调用子程序,其本质是流程转移,底层原理和JMP指令类似。

(2)指令格式:CALL <标号> 或CALL far ptr <标号> 或CALL <寄存器>。

①CPU执行“CALL <标号> ”指令时,会进行两步操作:

[1]将当前的IP压入栈中,即(sp) = (sp) - 2、((ss) * 16 + (sp)) = (IP)。

[2]转移到标号处执行指令,即(IP) = (IP) + 16位位移(16位位移 = <标号>处的地址 - CALL指令后的第一个字节的地址,范围为-32768~32767,用补码表示,它由编译程序在编译时算出)。

②CPU执行“CALL far ptr <标号> ”指令时,会进行两步操作:

[1]将当前的CS和IP依次压入栈中,即(sp) = (sp) - 2、((ss) * 16 + (sp)) = (CS)、(sp) = (sp) - 2、((ss) * 16 + (sp)) = (IP)。

[2]转移到标号处执行指令,(CS) = 标号所在的段地址、(IP) = 标号所在的偏移地址。

③CPU执行“CALL <16位寄存器> ”指令时,会进行两步操作:

[1]将当前的IP压入栈中,即(sp) = (sp) - 2、((ss) * 16 + (sp)) = (IP)。

[2]将IP寄存器中的值置为<16位寄存器>中的值

(3)除了上面三种指令格式以外,转移地址还可通过访问特定内存地址给出。

①CALL word ptr <内存单元地址>:<内存单元地址>指向一个字数据,该指令会将IP寄存器中的值改为<内存单元地址>中存放的一个字数据。

②CALL dword ptr <内存单元地址>:<内存单元地址>指向两个字数据,该指令会将IP寄存器中的值改为<内存单元地址>指向内存中低地址存放的字数据,将CS寄存器中的值改为<内存单元地址>指向内存中高地址存放的字数据。

2、RET指令和RETF指令

(1)RET指令可以没有操作数,它相当于指令“POP IP”,用栈中的数据修改IP的内容,从而实现近转移;RETF指令同样也可以没有操作数,它相当于指令“POP IP”与“POP CS”,用栈中的数据修改IP和CS的内容,从而实现远转移

(2)RET指令和RETF指令通常配合CALL指令使用,从而实现子程序的调用,后续主要使用RET指令进行介绍。

五、MUL指令

1、MUL指令的功能和用法

(1)MUL是乘法指令,使用MUL做乘法的时候,被乘数默认放在AL或AX中,乘数放在其它寄存器或内存单元中,积存放在AX或AX、DX中,具体如何放置见下表

被乘数的长度为8位

被乘数的长度为16位

被乘数存放的位置

AL

AX

乘数存放的位置

8位长度的内存单元或寄存器

16位长度的内存单元或寄存器

积存放的位置

AX

DX(积高16位)和AX(积低16位)

(2)MUL指令格式:MUL <寄存器或内存单元地址>。

(3)使用MUL指令时,切记提前在默认的寄存器中设置好被乘数,且默认寄存器不作别的用处。

2、MUL指令使用举例

(1)计算10×100:

assume cs:code

code segment

        main: mov al, 100   ;被乘数(8位)

                  mov bl, 10   ;乘数(8位)

                  mul bl

                  mov ax, 4c00h

                  int 21h

code ends

end main

(2)计算100×10000:

assume cs:code

code segment

        main: mov ax, 100   ;被乘数(16位)

                  mov bx, 1000   ;乘数(16位)

                  mul bl

                  mov ax, 4c00h

                  int 21h

code ends

end main

六、汇编语言的模块化程序设计

1、CALL指令和RET指令的配合使用

(1)CALL指令和RET指令配合使用,可以实现具有子程序的源程序,其中子程序可根据提供的参数处理一定的事务,处理后将结果(返回值)提供给调用者。

(2)具有子程序的源程序的框架:

assume cs:code, ss:stack

stack segment

                  db 16 dup (0)   ;为call和ret指令设置栈

stack ends

code segment

        main: ...

                  call sub1    ;调用子程序sub1

                  ...

                  mov ax, 4c00h

                  int 21h

        sub1: ...     ;子程序sub1开始

                  call sub2    ;调用子程序sub2

                  ...

                  ret     ;子程序返回

        sub2: ...     ;子程序sub2开始

                  ...

                  ret     ;子程序返回

code ends

end main

(3)举例:将AX中的值乘以2(AX的初始值为1000D),结果存放在AX寄存器中。

assume cs:code, ss:stack

stack segment

                  db 16 dup (0)   ;为call和ret指令设置栈

stack ends

code segment

        main: mov ax, stack

                  mov ss, ax   ;栈段地址初始化

                  mov sp, 16   ;栈顶指针初始化

                  mov ax, 1000   ;AX初始化

                  call s    ;调用子程序

                  mov ax, 4c00h

                  int 21h

        s:       add ax, ax   ;AX中的值乘以2,结果存放在AX中

                  ret     ;子程序返回

code ends

end main

2、参数和结果传递的问题

(1)例1:根据提供的N,计算N的3次方,要求将计算过程封装为一个子程序(或者说子函数)。

①解决方案——用寄存器与子函数完成参数和结果的传递:

[1]函数参数放到BX中,即(bx) = N。

[2]子程序中用多个MUL指令计算N^3。

[3]将结果放到DX和AX中。

②汇编程序:

assume cs:code, ds:data

data segment

                  dw 1,2,3,4,5,6,7,8  ;提供的N

                  dd 0,0,0,0,0,0,0,0  ;N的3次方计算结果保存在此处

data ends

code segment

        main: mov ax, data

                  mov ds, ax   ;数据段地址初始化

                  mov si, 0    ;DS:SI指向数据段第一个字

                  mov di, 16   ;DS:DI指向数据段保存计算结果区域的第一个双字

                  mov cx, 8

        s:       mov bx, [si]   ;将N通过BX传递给子程序

                  call cube    ;调用子程序进行计算

                  mov [di], ax   ;积的低16位存到计算结果区域的低地址字

                  mov [di].2, dx   ;积的高16位存到计算结果区域的高地址字

                  add si, 2    ;DS:SI指向数据段下一个字

                  add di, 4    ;DS:DI指向数据段保存计算结果区域的下一个双字

                  loop s

                  mov ax, 4c00h

                  int 21h

        cube: mov ax, bx

                  mul bx

                  mul bx

                  ret     ;子程序返回

code ends

end main

③缺陷:计算结果可能大于32位,甚至更大,如果全部借助寄存器进行传递,显然是不现实的。

(2)例2:将data段中的字符串转化为大写。

①解决方案——用内存单元与子函数完成参数和结果的传递

[1]将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序

[2]对于具有批量数据的返回结果,也可用同样的方法。

②汇编程序:

assume cs:code, ds:data

data segment

                  db ‘conversation’

data ends

code segment

        main: mov ax, data

                  mov ds, ax    ;数据段地址初始化

                  mov si, 0     ;DS:SI指向数据段第一个字

                  mov cx, 12    ;总共需要转换12个字母

                  call capital    ;调用子程序进行大写字母转换

                  mov ax, 4c00h

                  int 21h

        capital: and byte ptr [si], 11011111b ;将DS:SI指向的字母转换为大写

                  inc si     ;(si) = (si) + 1

                  loop capital

                  ret      ;子程序返回

code ends

end main

(2)例3:计算(a-b)的3次方,假设a = 3、b = 1。

①解决方案——用栈与子函数完成参数的传递:由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数

②知识补充:“RET idata”指令的含义为“pop ip”、“add sp, n”,栈顶指针sp下移后,虽然栈中的数据没有被删除,但它们在逻辑上相当于被删除了,不会影响栈的正常运作;“RETF idata”同理,这里不再赘述

③汇编程序:

assume cs:code

code segment

        main: mov ax, 1

                  push ax     ;参数b压入栈中

                  mov ax, 3

                  push ax     ;参数a压入栈中

                  call difcube    ;调用子程序进行算式计算

                  mov ax, 4c00h

                  int 21h

        difcube: push bp  ;要借用BP寄存器,首先需要保护其原本的内容,将其入栈

                  mov bp, sp ;获取栈顶指针,存在BP寄存器中

                  mov ax, [bp+4] ;获取参数a,将其存入AX中

                  sub ax, [bp+6]  ;获取参数b,计算a-b,将其存入AX中

                  mov bp, ax

                  mul bp

                  mul bp

                  pop bp  ;BP寄存器借用完成,将其原本的内容从栈顶还原回去

                  ret 4   ;子程序返回,并逻辑删除形式参数a和b

code ends

end main

④程序的执行过程中栈的变化:

七、寄存器冲突问题

1、可能引起冲突的情况

(1)在编写子程序时,子程序使用了主程序正在使用的寄存器,且寄存器中有主程序后续需要使用到的数据,这种情况下子程序很可能会影响主程序的正常运作。

(2)举例:将data段中的字符串转化为大写。

assume cs:code, ds:data

data segment

                  db ‘word’, 0    ;’\0’是字符串的结束标志

                  db ‘byte’, 0    ;’\0’是字符串的结束标志

                  db ‘uint’, 0    ;’\0’是字符串的结束标志

                  db ‘char’, 0    ;’\0’是字符串的结束标志

data ends

code segment

        main: mov ax, data

                  mov ds, ax    ;数据段地址初始化

                  mov bx, 0

                  mov cx, 4     ;需要转换4个单词

        s:       mov si, bx    ;DS:SI指向字符串首地址

                  call capital    ;调用子程序对一个单词进行大写字母转换

                  add bx, 5     ;使DS:SI指向下一个字符串首地址

                  loop s

                  mov ax, 4c00h

                  int 21h

        capital: mov cl, [si]    ;主程序中的循环结构正在用CX寄存器

                  mov ch, 0     ;子程序直接占用CX寄存器显然是有问题的

                  jcxz ok     ;判断DS:SI指向的是否是’\0’,是则结束

                  and byte ptr [si], 11011111b ;将DS:SI指向的字母转换为大写

                  inc si     ;DS:SI指向下一个字母

                  jmp short capital

        ok:     ret      ;子程序返回

code ends

end main

2、冲突问题的解决

(1)冲突问题有两种解决方案,如下所示,最为常用的是第二种,使用第二种方案时,编写调用了程序的程序的时候不必关心子程序到底使用了哪些寄存器,编写子程序的时候也不必关心调用者使用了哪些寄存器。

①在编写调用子程序的程序时,程序员需要注意子程序中有没有用到会产生冲突的寄存器,如果有,使用别的寄存器,不要使用会产生冲突的寄存器。

在子程序的开始将要用到的所有寄存器中的内容都保存起来,在子程序返回前再恢复

(2)子程序标准框架:

子程序开始: 子程序中使用的寄存器入栈

                    子程序内容

                    子程序使用的寄存器出栈

                    返回(ret、retf)

(3)寄存器冲突问题的解决示例:将data段中的字符串转化为大写。

assume cs:code, ds:data

data segment

                  db ‘word’, 0    ;’\0’是字符串的结束标志

                  db ‘byte’, 0    ;’\0’是字符串的结束标志

                  db ‘uint’, 0    ;’\0’是字符串的结束标志

                  db ‘char’, 0    ;’\0’是字符串的结束标志

data ends

code segment

        main: mov ax, data

mov ds, ax    ;数据段地址初始化

mov bx, 0

mov cx, 4     ;需要转换4个单词

s:  mov si, bx    ;DS:SI指向字符串首地址

call capital    ;调用子程序对一个单词进行大写字母转换

add bx, 5     ;使DS:SI指向下一个字符串首地址

loop s

mov ax, 4c00h

int 21h

capital: push cx

push si

change: mov cl, [si]    ;将DS:SI指向的字符送入CL

mov ch, 0     ;CH置0

jcxz ok     ;判断DS:SI指向的是否是’\0’,是则结束

and byte ptr [si], 11011111b ;将DS:SI指向的字母转换为大写

inc si     ;DS:SI指向下一个字母

jmp short capital

ok:  pop si

pop cx

ret      ;子程序返回

code ends

end main

八、标志寄存器

1、标志寄存器介绍

(1)标志寄存器的简写为PSW或FLAGS,又称程序状态字。

(2)标志寄存器的结构:

①FLAGS寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息

②8086CPU中没有使用FLAGS的1、3、5、12、13、14、15位,这些位不具有任何含义。

(3)标志寄存器的作用:

用来存储相关指令的某些执行结果

用来为CPU执行相关指令提供行为依据

用来控制CPU的相关工作方式

(4)直接访问标志寄存器的方法:

①pushf指令:将标志寄存器的值压栈。

②popf指令:从栈中弹出数据,送入标志寄存器中。

(5)在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如add、sub、mul、div、inc、or、and等,它们大都是运算指令,进行逻辑或算术运算;有的指令的执行对标志寄存器没有影响,比如mov、push、pop等,它们大都是传送指令。

(6)使用一条指令的时候,要注意这条指令的全部功能,其中包括执行结果对标记寄存器的哪些标志位造成影响。

2、ZF-零标志

(1)ZF标记相关指令的计算结果是否为0

①ZF=1,表示“结果是0”,1表示“逻辑真”

②ZF=0,表示“结果不是0”,0表示“逻辑假”。

(2)举例:

;and指令一共两个操作数,它对两个操作数执行按位与操作,结果存在操作数1中

mov ax, 1

and ax, 0   ;ZF = 1,表示“结果是0”

;or指令一共两个操作数,它对两个操作数执行按位或操作,结果存在操作数1中

mov ax, 1

or ax, 0   ;ZF = 0,表示“结果不是0”

3、PF-奇偶标志

(1)一条指令执行后,结果的所有二进制位中,若1的个数为偶数,PF = 1,否则若1的个数为奇数,PF = 0

(2)举例:

mov al, 1

add al, 10   ;计算结果为0000 1011B,其中有3个1,PF = 0

mov al, 1

or al, 2   ;计算结果为0000 0011B,其中有2个1,PF = 1

4、SF-符号标志

(1)一条指令执行后,将结果视为有符号数,若结果为负,SF = 1,否则若结果为非负,SF = 0

(2)举例:

mov al, 10000001b

add al, 1   ;计算结果为1000 0010B,为负数,SF = 1

sub ax, ax  ;计算结果为0000 0000 0000 0000B,为正数,SF = 0

(3)SF标志是CPU对有符号数运算结果的一种记录,将数据当作有符号数来运算的时候,通过SF可知结果的正负,将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值。

5、CF-进位标志

(1)对于位数为N的无符号数来说,其对应的二进制信息的最高位即第N-1位,是最高有效位,假想存在的第N位,就是相对最高有效位的更高位。

(2)在进行无符号数运算的时候,CF记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值一条指令执行后,若过程中有进位或借位,CF=1,否则若过程中无进位或借位,CF=0

(3)举例:

mov al, 98h

add al, al   ;计算过程产生进位,(al) = 30H,CF = 1

add al, al   ;计算过程未产生进位或借位,(al) = 60H,CF = 0

sub al, 98h  ;计算过程产生借位,(al) = C8H,CF = 1

6、OF-溢出标志

(1)在进行有符号数运算的时候,如结果超过了机器所能表示的范围称为溢出有符号数操作指令执行后,若有溢出,OF = 1,否则若无溢出,OF = 0

(2)举例:

mov al, 98

add al, 99   ;98 + 99 = 197,超出了8位有符号数的表示范围,OF = 1

mov al, 0f0h

add al, 88h  ;(-16) + (-120) = -136,超出了8位有符号数的表示范围,OF = 1

九、带进(借)位的加(减)法

1、ADC指令

(1)ADC是带进位加法指令,它利用了CF位上记录的进位值,其功能与ADD指令类似,不同的是,它在将两个操作数相加的同时还会加上CF标志的值

(2)指令格式:ADC <操作数1>, <操作数2>。

(3)举例:

①目的:编写一个子程序,对两个128位数据(低地址存放数的低位)进行相加,运算结果存储在第一个数的存储空间中。

②汇编程序:

assume cs:code, ds:data

data segment

                  dw 0A452H,0A8F5H,78E6H,0A8EH,8B7AH,54F6H,0F04H,671EH

                  dw 0E71EH,0EF04H,54F6H,8B7AH,0A8EH,78E6H,58F5H,0452H

data ends

code segment

        main: mov ax, data

                  mov ds, ax    ;数据段地址初始化

                  mov si, 0     ;DS:SI指向第一个操作数首地址

                  mov di, 16    ;DS:DI指向第二个操作数首地址

                  mov cx, 8     ;需要进行8次加法

                  call add128    ;调用子程序进行加法

                  mov ax, 4c00h

                  int 21h

        add128: push ax

                  push cx

                  push si

                  push di

                  sub ax, ax    ;进位标志清零

        s:       mov ax, [si]

                  adc ax, [di]    ;带进位加法

                  mov [si], ax    ;将结果保存在第一个数的存储空间中

                  inc si     ;DS:SI指向操作数高位

                  inc si     ;此处不可用add指令,会影响CF

                  inc di     ;DS:SI指向操作数高位

                  inc di     ;此处不可用add指令,会影响CF

                  loop s

                  pop di

                  pop si

                  pop cx

                  pop ax

                  ret      ;子程序返回

code ends

end main

2、SBB指令

(1)SBB是带借位减法指令,它利用了CF位上记录的减位值,其功能与SUB指令类似,不同的是,它在将两个操作数相减的同时还会减去CF标志的值

(2)指令格式:SBB <操作数1>, <操作数2>。

(3)举例:

①目的:计算003E1000H-00202000H,结果放在AX和BX中。

②汇编程序:

assume cs:code

code segment

        main: mov bx, 1000h   ;被减数低16位存入BX

                  mov ax, 003eh    ;被减数高16位存入AX

                  sub bx, 2000h    ;低16位先做减法

                  sbb ax, 0020h    ;高16位再做减法,并带上借位

                  mov ax, 4c00h

                  int 21h

code ends

end main

十、CMP指令与条件转移指令

1、CMP指令

(1)指令格式:CMP <操作数1>, <操作数2>。

(2)CMP指令会计算操作数1减去操作数2的结果,但它并不保存结果,也不改变两个操作数,仅仅影响标志寄存器,其它相关指令(通常是条件转移指令)通过识别这些被影响的标志寄存器位可以得知两个操作数的比较结果

(3)无符号数比较与标志位取值(以指令“CMP AX, BX”为例):

(4)有符号数比较与标志位取值(以指令“CMP AX, BX”为例):

2、条件转移指令

(1)几个英文字母的含义:

(2)根据单个标志位转移的指令:

(3)根据无符号数比较结果进行转移的指令:

(4)根据有符号数比较结果进行转移的指令:

3、CMP指令与条件转移指令的配合使用举例

(1)目标:子程序If_else实现逻辑“如果(ah)=(bh),则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)”。

(2)子程序实现:

If_else: cmp ah, bh ;进行两数比较

                je s   ;如果两数相等,跳转至标号s,否则顺序执行

                add ah, bh ;不满足条件执行的语句

                jmp short ok ;跳转至后面,避免同时执行了两个分支的操作

         s:    add ah, ah ;满足条件执行的语句

         ok:  ret

十一、串传送

1、DF-方向标志

(1)该标志位用来控制CPU的相关工作方式,具体表现如下:

①DF = 0:每次操作后SI寄存器、DI寄存器后,SI寄存器、DI寄存器的值自增。

②DF = 1:每次操作后SI寄存器、DI寄存器后,SI寄存器、DI寄存器的值自减。

(2)对DF位进行设置的指令:

①CLD指令:将标志寄存器的DF位设为0(clear)。

②STD指令:将标志寄存器的DF位设为1(setup)。

2、串传送指令

(1)在前面举过将字符串复制到另一存储空间中的例子,当时的解决方法是程序员人为地通过寄存器将字符一位一位地进行传送,实际上要想实现这种串传送的功能,还可以借助串传送指令。

(2)执行一次以字节为单位传送的串传送指令——MOVSB会发生的事情:

①((es) * 16 + (di)) = ((ds) * 16 + (si))。

②若DF = 0,则(si) = (si) + 1、(di) = (di) + 1;若DF = 1,则(si) = (si) - 1、(di) = (di) - 1。

(3)执行一次以字为单位传送的串传送指令——MOVSW会发生的事情:

①((es) * 16 + (di)) = ((ds) * 16 + (si))。

②若DF = 0,则(si) = (si) + 2、(di) = (di) + 2;若DF = 1,则(si) = (si) - 2、(di) = (di) - 2。

3、REP指令

(1)REP指令常与串传送指令搭配使用。

(2)REP指令无操作数,它会根据CX中的值,重复执行它后面的指令(也能认为这个指令是REP指令的操作数)。

4、串传送应用举例

(1)目的:用串传送指令,将F000H段中的最后16个字符复制到data段中。

(2)汇编程序:

assume cs:code, ds:data

data segment

                  db 16 dup (0)

data ends

code segment

        main: mov ax, 0f000h

                  mov ds, ax

                  mov si, 0ffffh    ;F000:FFFFH是F000H段的最后一字节地址

                  mov ax, data    ;获取数据段地址

                  mov es, ax    ;数据段地址送入ES中

                  mov di, 15    ;ES:DI指向data段最后一字节

                  mov cx, 16    ;需要进行16次串传送

                  std      ;将DF位设为1,操作(si)和(di)时它们会递减

                  rep movsb    ;进行16次串传送

                  mov ax, 4c00h

                  int 21h

code ends

end main

相关文章:

  • 源道-做股票应该从传统的技术图形转向行业规律思考
  • C++(23)—模板初阶
  • java面试篇(常见的集合底层原理)
  • AI大模型之模型幻觉
  • Redis——通信协议
  • 【大模型】单选数据集制作举例
  • Java技术栈 —— 网络带宽受限,图片预览加速
  • 5.4.1 Password控件的Password属性绑定问题
  • 06 GE Modifier
  • 【AI论文】对人工智能生成文本的稳健和细粒度检测
  • 性价比超高的 英伟达Tesla T4卡 如何解决散热问题?
  • Ubuntu 22.04 更换 Nvidia 显卡后启动无法进入桌面问题的解决
  • OpenAPI 3.0学习笔记
  • 【Redis】了解Redis
  • Java Web项目(一)
  • Java29:Spring MVC
  • 积木报表查询出现jdbc.SQLServerException: 对象名 ‘user_tab_comment 的解决方法
  • Federated Weakly Supervised Video Anomaly Detection with Multimodal Prompt
  • SpringBoot集成Kafka详解
  • 【锂电池SOH估计】SVM支持向量机锂电池健康状态估计,锂电池SOH估计(Matlab完整源码和数据)
  • 普京签署法律,诋毁俄军将面临最高7年监禁
  • 马文化体验展商圈启动,环球马术冠军赛的能量不止在赛场
  • “雷公”起诉人贩子王浩文案将开庭:索赔6元,“讨个公道”
  • 重大虚开发票偷税骗补案被查处:价税2.26亿,涉700余名主播
  • 9厘米,25克!最小最轻的无线陆空两栖机器人来了
  • 海拔四百公里的救赎