汇编语言程序设计-5-流程转移与子程序

news2024/12/23 10:29:57

5. 流程转移与子程序

文章目录

  • 5. 流程转移与子程序
    • 5.0 导学
    • 5.1 “转移”综述
    • 5.2 操作符offset
    • 5.3 jmp指令
    • 5.4 其他转移指令-jcxz、loop
    • 5.5 call指令和ret指令
    • 5.6 call和ret的配合使用
    • 5.7 mul指令
    • 5.8 汇编语言的模块化程序设计
    • 5.9 寄存器冲突的问题-子程序标准框架
    • 5.10 标志寄存器
    • 5.11 带进(借)位的加减法-adc、sbb指令
    • 5.12 cmp和条件转移指令
    • 5.13 条件转移指令应用
    • 5.14 DF标志和串传送指令

  • 参考视频:烟台大学贺利坚老师的网课《汇编语言程序设计系列专题》,或者是B站《汇编语言程序设计 贺利坚主讲》,大家一起看比较热闹。
  • 中文教材:《汇编语言-第3版-王爽》(课程使用)、《汇编语言-第4版-王爽》(最新版)。
  • 老师的博客:《迂者-贺利坚的专栏-汇编语言》
  • 检测点答案参考:《汇编语言》- 读书笔记 - 各章检测点归档

本篇笔记对应课程第五章(下图倾斜),章节划分和教材对应关系如下。


5.0 导学

参考教材第九章:聚焦各种转移指令,来构造循环,会使用 offset计算出标识符的偏移地址。
【5.1 “转移”综述】
【5.2 操作符offset】
【5.3 jmp指令】
【5.4 其他转移指令】

参考教材第十章:介绍“模块化程序设计”,并使用 mul指令引出寄存器冲突问题。
【5.5 call指令和ret指令】
【5.6 call和ret的配合使用】
【5.7 mul指令】
【5.8 汇编语言的模块化程序设计】
【5.9 寄存器冲突的问题】

参考教材第十一章:重点介绍标志寄存器,并利用其实现更多的功能。
【5.10 标志寄存器】很多指令会影响其变化。
【5.11 带进(借)位的加减法】
【5.12 cmp和条件转移指令】
【5.13 条件转移指令应用】
【5.14 DF标志和串传送指令】

5.1 “转移”综述

  一般情况下,指令是顺序地逐条执行的,但在实际中经常需要改变程序的执行流程。可以修改IP、或同时修改 CS 和 IP 的指令统称为“转移指令”。概括地讲,转移指令就是可以控制CPU执行内存中某处代码的指令。下面给出转移指令的分类:

  1. 按转移行为:
  • 段内转移:只修改IP,如 jmp ax
  • 段间转移:同时修改CS和IP,如 jmp 1000:0
  1. 根据指令对IP修改的范围不同
  • 段内短转移:IP修改范围为-128~127
  • 段内近转移:IP修改范围为-32768~32767
  1. 按转移指令
  • 无条件转移指令:如 jmp
  • 条件转移指令:如 jcxz
  • 循环指令:如 loop
  • 过程
  • 中断

5.2 操作符offset

  有时候需要定位程序中某个标号的偏移地址,但是不同的系统编译后可能都不一样。此时可以用操作符 offset来取得标号的偏移地址,格式为 offset 标号。下面是一个示例:

【代码示例】有如下程序段,在下划线处添写2条指令,使该程序在运行中将 s 处的一条指令复制到 s0 处。
提示:mov ax,bx指令的长度为两个字节,即1个字。复制指令本质上就是复制二进制码。

答:

mov ax,cs:[si]  ; 将s处的指令移到ax中
mov cs:[di],ax  ; 再将其存储到s0处

5.3 jmp指令

  jmp指令的功能是实现无条件转移,可以只修改IP(段内转移),也可以同时修改CS和IP(段间转移)。jmp指令通过给出转移的 目的地址 或 相对距离 来实现跳转,具体根据指令格式或跳转距离有所不同。下面是 jmp指令的使用总结:

  1. jmp 标号】跳转到标号所在的位置。
  • 段内短转移:格式为 jmp short 标号,实现8位补码的相对位移,跳转范围是-128~127。
  • 段内近转移:格式为 jmp near ptr 标号,实现16位补码的相对位移,跳转范围是-32768~32767。
  • 段间转移(远转移):格式为 jmp far ptr 标号,跳转范围在8位补码范围内给出相对位移,超过8位补码范围后则会直接给出目标地址。
  1. jmp 寄存器】令 IP=(寄存器),实现16位的段内位移,比如 jmp bx
  2. jmp 内存单元】跳转到的内存单元中给出的地址。
  • 段内转移:格式为 jmp word ptr 内存单元地址,令 IP=(内存单元),比如 jmp word ptr [bx]
  • 段间转移:格式为 jmp dword ptr 内存单元地址,令 CS=(高位字)、IP=(低位字),比如 jmp dword ptr [bx]
  1. jmp 段地址:偏移地址】禁止在源程序中使用,只有在debug模式下才能使用。

注:若要跳转的位置超出范围,编译器会报错。

上述用法中,第二、三、四种用法都是直接给出了要跳转到的目标地址,但只有第一种 jmp 标号的段内转移需要由编译器计算要跳转的相对位置。第一种用法的三种使用示例:

5.4 其他转移指令-jcxz、loop

jcxz 标号】(jmp cx=zero)
功能:有条件转移指令。如果 (cx)=0,则转移到标号处执行;如果 (cx)≠0,什么也不做(程序向下执行)。
转移范围:当(cx)=0时,(IP)=(IP)+8位补码位移,所以转移范围是-128~127,8位补码位移由编译程序在编译时算出。

loop 标号
功能:有条件转移指令。如果 (cx)≠0,则转移到标号处执行;如果 (cx)=0,程序向下执行。
转移范围:如果(cx)≠0,(IP)=(IP)+8位补码位移,所以转移范围是-128~127。

注:所有的“有条件转移指令”都是短转移(8位补码)

最后总结一下,jmp short 标号jmp near ptr 标号jcxz 标号loop 标号的机器码都是根据相对地址来进行转移的。这样做的好处是方便了程序段在内存中的浮动装配。无论要跳转的指令的实际地址是多少,指令转移的相对位移是不变的。


5.5 call指令和ret指令

call指令的功能是调用子程序,本质上是流程转移,也就是修改 CS、IP。下面是其具体使用格式:

  1. call 标号
  • 段内转移:格式是 call 标号。将 IP 压栈,然后令 (IP) = (IP) + 16位补码位移,跳转范围是-32768~32767,该位移由编译器计算得出。
  • 段间转移:格式是 call far ptr 标号。依次将 CS、IP 压栈,并令 CS:IP = 标号对应的段地址:偏移地址。
  1. call 寄存器】将当前IP值压栈,然后令 IP=(寄存器),实现16位的段内跳转。相当于 push IPjmp 寄存器

  2. call 内存单元

  • 段内转移:格式是 call word ptr 内存单元地址。将 IP 压栈,然后令 IP=(内存单元) 实现跳转。
  • 段间转移:格式是 call dword ptr 内存单元地址。依次将 CS、IP 压栈,然后令 CS=(高位字)、IP=(低位字)。

ret/retf指令的功能是返回,可以单独使用进行流程转移,但一般会和上一个 call配合使用:

  • 段内返回(近转移):格式是 ret,本质上就是将 IP 出栈,相当于 pop IP
  • 段间返回(远转移):格式是 retf,本质上是依次将 IP、CS 出栈,相当于 pop IPpop CS

注:若 ret/retf后跟了一个常数 n,表示 栈顶指针寄存器SP+n,表示忽略 n 字节的栈内参数。

5.6 call和ret的配合使用

本小节演示如何成对调用 callret指令,来实现子程序的调用和返回,注意需要设置栈

【代码示例】计算 2 的 N 次方(N<16),计算前 N 的值由 CX 提供,结果保存在DX中。

assume ss:stacksg,cs:codesg

stacksg segment
        db 16 dup(0)
stacksg ends

codesg segment
main:  
        ; 栈段初始化
        mov ax,stacksg
        mov ss,ax
        mov sp,16

        mov cx,3
        call power

        ; 程序退出
        mov ax,4c00h
        int 21h

        ; 子函数:计算2的N次方,N由CX给出
power:  mov dx,1
    s:  add dx,dx
        loop s
        ret
codesg ends
end main

5.7 mul指令

  和除法指令 div类似,乘法指令 mul也只有一个操作数——乘数,被乘数则默认存储在DX/AX 中,使用格式为 mul 寄存器mul 内存单元地址。若为寄存器则可以直接参考寄存器长度,否则需要程序员使用 byte ptrword ptr指定乘法位数。使用乘法指令时,切记提前在默认寄存器中设置好“被乘数”,且默认寄存器不作别的用处,四个操作数的说明:

  • 乘数:由操作数给出所在位置,若为内存单元必须指出数据长度。
  • 被乘数:8位乘法保存在AL;16位乘法保存在AX。
  • 结果:8位乘法保存在AX;16位乘法中高16位在DX、低16位在AX。
表5-1 乘法指令的示例
示例指令被乘数乘数结果
8位乘法mul bl(al)(bl)(ax)
mul byte ptr ds:[0](al)((ds)*16+0)(ax)
mul byte ptr [bx+si+8](al)((ds)*16+(bx)+(si)+8)(ax)
16位乘法mul bx(ax)(bx)(dx)*10000H+(ax)
mul word ptr es:[0](ax)((ds)*16+0)(dx)*10000H+(ax)
mul word ptr [bx+si+8](ax)((ds)*16+(bx)+(si)+8)(dx)*10000H+(ax)

【代码示例1】计算100*10。
分析:100和10小于255,选择8位乘法。

mov al,100
mov bl,10
mul bl

【代码示例2】计算100*10000。
分析:100小于255,可10000大于255,所以必须做16位乘法。

mov ax,100
mov bx,10000
mul bx

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

5.6小节中已经演示了如何使用 callret指令实现子程序的调用和返回,其中参数传递的方法有:

  • 用寄存器传递参数:代码最简便,但寄存器数量有限,不能大量存储数据。
  • 用内存单元传递参数:需要定义数据段,常用于批量的存储或传递数据。
  • 用栈传递参数:需要定义栈段,但注意及时入栈、出栈,不要影响程序的正常退出。

下面使用代码来演示这三种方法的不同:

【代码示例1-寄存器传参】根据提供的N,计算N的3次方。
提示:N 存储在 BX 中,根据乘法指令性质,计算结果直接就存储在 DX、AX 中。

assume ss:stacksg,cs:codesg

stacksg segment
        db 16 dup(0)
stacksg ends

codesg segment
main:  ; 栈段初始化
        mov ax,stacksg
        mov ss,ax
        mov sp,16

        mov bx,5
        call sub1

        ; 程序退出
        mov ax,4c00h
        int 21h

        ; 子函数:计算 BX 的三次方
sub1:   mov ax,bx
        mul bx
        mul bx
        ret
codesg ends
end main

【代码示例2-内存单元传参】据提供的N,计算N的3次方。
提示:N 存储在数据段第一个字,结果保存在数据段的第二、三个字。

assume ds:datasg,ss:stacksg,cs:codesg

datasg segment
        db 16 dup(0)
datasg ends

stacksg segment
        db 16 dup(0)
stacksg ends

codesg segment
main:   ; 数据段初始化
        mov ax,datasg
        mov ds,ax
        ; 栈段初始化
        mov ax,stacksg
        mov ss,ax
        mov sp,16

        mov byte ptr ds:[0],5
        call sub1

        ; 程序退出
        mov ax,4c00h
        int 21h

        ; 子函数:计算三次方
sub1:   mov bx,ds:[0]  ; 取出数据
        mov ax,bx
        mul bx
        mul bx
        mov ds:[2],dx  ; 存储高16mov ds:[4],ax  ; 存储低16位
        ret
codesg ends
end main

【代码示例3-栈传参】计算( a – b ) ^ 3 ,a、b 为 word 型数据。
提示:进入子程序前,参数a、b入栈;程序内使用偏移地址访问数据;计算结果保存在数据段。
难点:将栈顶指针sp存入bp中,就可以使用 [bp+idata] 访问栈内数据。

assume ds:datasg,ss:stacksg,cs:codesg

datasg segment
        db 16 dup(0)
datasg ends

stacksg segment
        db 16 dup(0)
stacksg ends

codesg segment
main:   ; 数据段初始化
        mov ax,datasg
        mov ds,ax
        ; 栈段初始化
        mov ax,stacksg
        mov ss,ax
        mov sp,16

        mov ax,3
        push ax  ; a入栈
        mov ax,1
        push ax  ; b入栈
        call sub1

        ; 程序退出
        mov ax,4c00h
        int 21h

        ; 子函数:计算三次方
sub1:   mov bp,sp         ; bp专用于栈的偏移地址计算
        mov bx,ss:[bp+4]  ; 取出a
        sub bx,ss:[bp+2]  ; a-b
        mov ax,bx
        mul bx
        mul bx
        mov ds:[0],dx  ; 存储高16mov ds:[2],ax  ; 存储低16位
        ret 4  ; 本质上为 sp+4,相当于a,b出栈
codesg ends
end main

注:bp 如果有其他用途,可以先入栈、用完再出栈,只影响栈内的相对索引。

5.9 寄存器冲突的问题-子程序标准框架

图5-1 子程序的标准框架

  上一小节的最后一个代码示例说明了在子程序中调用寄存器可能会与主程序的寄存器产生冲突,所以子程序中一定要将用到的寄存器入栈,子程序退出前出栈。下面是一个主程序和子程序都调用CX寄存器的示例:

【代码示例】编程将data段中的五行字符串,全部转化为大写。字符串长度不定长,但一行长度固定为8字节,剩余字节补0(见程序)。
关键点:子程序内判断字符串结束标志用 jcxz,也就是CX是否为0。

assume ds:datasg,ss:stacksg,cs:codesg

datasg segment
    db 'flag',0,0,0,0
    db 'compare',0
    db 'app',0,0,0,0,0
    db 'computer'
    db 'register'
datasg ends

stacksg segment
    db 16 dup(0)
stacksg ends

codesg segment
; 主程序
main:   ; 数据段初始化
        mov ax,datasg
        mov ds,ax
        ; 栈段初始化
        mov ax,stacksg
        mov ss,ax
        mov sp,16

        mov cx,5  ; 5行字符串
        mov bx,0  ; 字符串的起始位置
    s:  call sub1
        add bx,8  ; 每一行长度为8
        loop s

        ; 程序退出
        mov ax,4c00h
        int 21h

; 子程序1:将字符串全部转换为大写
sub1:   ; 寄存器入栈
        push si
        push cx
        
        ; 大写转换:cx存储字符,si字符索引
        mov si,bx
sub1_1: mov cl,ds:[si]
        mov ch,0
        jcxz sub1_2  ; 若cx=0就结束程序
        and cl,11011111b  ; 大写转换
        mov ds:[si],cl
        inc si
        jmp short sub1_1

        ; 寄存器出栈
sub1_2: pop cx
        pop si
        ret
codesg ends
end main

5.10 标志寄存器

8086CPU有14个寄存器:

  • 通用寄存器:AX、BX、CX、DX
  • 变址寄存器:SI、DI
  • 指针寄存器:SP、BP
  • 指令指针寄存器: IP
  • 段寄存器:CS、SS、DS、ES
  • 标志(flag)寄存器:PSW/FLAGS

  本节介绍8086CPU中的标志寄存器 FLAGS,也称为程序状态字。标志寄存器可用于存储相关指令的某些执行结果、为CPU执行相关指令提供行为依据、控制CPU的相关工作方式等。FLAGS 寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。有含义的位如下图所示:

图5-2 8086CPU中的状态寄存器
  • OF-溢出标志(Overflow Flag):将运算当作有符号数运算,若结果溢出则OF=1、无溢出则OF=0。
  • DF-方向标志位(Direction Flag):在串处理指令中,控制每次操作后si,di的增减。具体见“5.14节-DF标志和串传送指令”。
  • SF-符号标志(Sign Flag):运算指令执行后,将结果视为有符号数(补码),结果为负数则SF=1、非负数则SF=0。
  • ZF-零标志(Zero Flag):运算指令执行后,计算结果是否为0。结果为0则ZF=1、结果非0则ZF=0。
  • PF-奇偶标志(Parity Flag):运算指令执行后,结果的所有二进制位中1的个数。偶数则PF=1,奇数则PF=0。
  • CF-进位标志(Carry Flag):将运算当作无符号数运算,有向更高位的进位或借位则CF=1、没有则CF=0。

注:OF和CF在含义上几乎一致,但是OF针对有符号数运算、CF针对无符号数运算。
注:有符号数(补码)运算后,双符号位不同则为“溢出”;无符号数计算后,超出范围为“溢出”。——《计算机组成原理》
注:无符号数运算时,SF的值没有意义,但是运算结果仍然会影响SF。

  在8086CPU的指令集中,影响标志寄存器的大都是运算指令,进行逻辑或算术运算;比如 addsubmuldivincorand等。对标志寄存器没有影响的大都是传送指令,比如 movpushpop等。使用一条指令的时候,要注意这条指令的全部功能,其中包括执行结果对标记寄存器的哪些标志位造成影响。最后给出直接访问标志寄存器的方法:

  • pushf:将标志寄存器的值压栈。
  • popf:从栈中弹出数据,送入标志寄存器中。
  • debug模式下,直接使用 r指令可以查看标志寄存器的值。

注:inc不会影响进/借位标志位CF。

下面通过一个例子展示标志寄存器的变化:

【代码示例】使用下面的指令后,标志寄存器的变化。

指令结果溢出标志
OF
符号标志
SF
零标志
ZF
奇偶标志
PF
进位标志
CF
sub al,al00/NV0/PL1/ZR1/PE0/NC
mov al,10h0001_0000b-----
add al,90h1010_0000b0/NV1/NG0/NZ1/PE0/NC
mov al,80h1000_0000b-----
add al,80h1_0000_0000b1/OV0/PL1/ZR1/PE1/CY
mov al,0FCh1111_1100b-----
add al,05h1_0000_0001b0/NV0/PL0/NZ0/PO1/CY
mov al,7Dh0111_1101b-----
add al,0Bh1000_1000b1/OV1/NG0/NZ1/PE0/NC

5.11 带进(借)位的加减法-adc、sbb指令

adc 操作对象1,操作对象2
功能:将CF进位标志也包括在加法操作中,也就是 操作对象1=操作对象1+操作对象2+CF。主要应用于大数相加。

sbb 操作对象1,操作对象2
功能:将CF进位标志也包括在减法操作中,也就是 操作对象1=操作对象1-操作对象2-CF。主要应用于大数相减。

【代码示例1-32位相加】编程计算001E_F000H+0020_1000H,结果放在ax(高16位)和bx(低16位)中。
思路:先将低16位相加,然后将高16位和进位值相加。

【代码示例2-32位相减】计算 003E_1000H–0020_2000H,结果放在ax(高16位)和bx(低16位)中。

【代码示例3-128位相加】编写一个子程序,对两个128位数据进行相加,运算结果存储在第三个数的位置。
注:数据为128位,需要8个字单元,由低地址单元到高地址单元,依次存放由低到高的各个字(降低难度)。

assume ds:datasg,ss:stacksg,cs:codesg

datasg segment
        ; 低位→高位
        dw 0A452H,0A8F5H,78E6H,0A8EH,8B7AH,54F6H,0F04H,671EH
        dw 0E71EH,0EF04H,54F6H,8B7AH,0A8EH,78E6H,58F5H,0452H
        dw 8 dup(0)
datasg ends

stacksg segment
        db 16 dup(0)
stacksg ends

codesg segment
main:   ; 数据段初始化
        mov ax,datasg
        mov ds,ax
        ; 栈段初始化
        mov ax,stacksg
        mov ss,ax
        mov sp,16

        call add128

        ; 程序退出
        mov ax,4c00h
        int 21h

; 子程序:实现128位相加
add128: ; 寄存器入栈
        push ax  ; 中间结果
        push cx  ; 循环控制
        push si  ; 被加数、加数索引
        push di  ; 结果的索引
        
        ; 128位相加
        mov si,0
        mov di,32
        sub ax,ax  ; 清空进位标志位
        mov cx,8
    s:  mov ax,ds:[si]
        adc ax,ds:[si+16]
        mov ds:[di],ax
        inc si
        inc si  ; 不能写成 add si,2
        inc di  ; 因为inc不影响CF、add会影响CF
        inc di
        loop s

        ; 寄存器出栈
        pop di
        pop si
        pop cx
        pop ax
        ret
codesg ends
end main

5.12 cmp和条件转移指令

cmp 操作对象1,操作对象2
功能:比较两个对象,通过不保存结果的减法运算影响标志位,主要包括溢出标志位OF、符号标志位SF、零标志位ZF、进位标志位CF。其中,重点关注“等于”、“小于”、“大于”,剩下的三种情况都是由这三种情况推导得出,如下表。

比较类型比较关系(ax)?(bx)(ax)-(bx)特点[OF,SF,ZF,CF]
无符号数比较等于(ax)=(bx)(ax)-(bx)=0[-,-,1,-]
小于(ax)<(bx)(ax)-(bx)要借位,且结果不为0[-,-,0,1]
大于(ax)>(bx)(ax)-(bx)不借位,且结果不为0[-,-,0,0]
不等于(ax)≠(bx)(ax)-(bx)≠0ZF=0
大于等于(ax)≥(bx)(ax)-(bx)不借位CF=0
小于等于(ax)≤(bx)(ax)-(bx)或者借位,或者结果为0ZF=1或CF=1
有符号数比较
(默认(ax)、(bx)均为负数)
等于(ah)=(bh)(ah)-(bh)=0[-,-,1,-]
小于(ax)<(bx)(ax)-(bx)为负,且不溢出[0,1,-,-]
大于(ax)>(bx)(ax)-(bx)为负,且溢出[1,1,-,-]
不等于(ah)≠(bh)(ah)-(bh)≠0ZF=0
大于等于(ax)≥(bx)(ax)-(bx)为非负,且无溢出OF=0且SF=0
小于等于(ax)≤(bx)(ax)-(bx)为非负,且有溢出OF=1或SF=0

jxxx 标号
功能:条件转移指令,满足条件后就会跳转到标号处。可以根据下面的缩写任意组合。
缩写说明:

类型指令跳转条件标志位类型指令跳转条件标志位
单个标志位jz结果为0ZF=1无符号数比较结果je/jz相等/结果为0ZF=1
jnz结果非0ZF=0jne/jnz不相等/结果非0ZF=0
js结果为负SF=1jb/jnae/jc低于/不高于等于/有借位CF=1
jns结果非负SF=Ojnb/jae/jnc不低于/高于等于/无借位CF=0
jo结果溢出OF=1 ja/jnbe高于/不低于等于CF=0且ZF=0
jno结果未溢出OF=0jna/jbe不高于/低于等于CF=1或ZF=1
jp奇偶位有效PF=1有符号数比较结果je/jz相等/结果为0ZF=1
jnp奇偶位无效PF=0jne/jnz不相等/结果非0ZF=0
jc有借位CF=1jl/jnge低于/不大于等于SF=1且OF=0
jnc无借位CF=0jnl/jge不小于/大于等于SF=0且OF=0
注:G-greater、L-less 专用于有符号数的大小比较;
A-above、B-below 专用于无符号数的大小比较。
jle/jng小于等于/不大于SF=0或OF=1
jnle/jg不小于等于/大于SF=1且0F=1

显然,将 cmpjxxx组合使用,就可以实现高级语言中if语句的功能,下面是一个示例。

【代码示例1-单分支】如果(ax)=0,则(ax)=(ax)+1

; 单分支结构
    add ax,0
    jnz ok
    inc ax
; 分支结构后的语句
ok: ...

【代码示例2-双分支】如果(ah)=(bh),则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)

; 双分支结构
    cmp ah,bh
    je s  ; 若相等就跳转到s
    add ah,bh     ; 不等就执行这条语句
    jmp short ok  ; 执行完就结束
s:  add ah,ah
; 分支结构后的语句
ok: ...

5.13 条件转移指令应用

【代码示例-三分支】给出8个字节的数据 db 18,11,18,1,18,5,63,38,统计其中小于18、等于18、大于18的字节个数,并保存到后续的内存单元中。

assume ds:datasg,cs:codesg

datasg segment
        db 18,11,18,1,18,5,63,38
        dw 8 dup(0)
datasg ends

codesg segment
main:   ; 数据段初始化
        mov ax,datasg
        mov ds,ax

        ; 开始统计
        mov al,0  ; 小于的个数
        mov ah,0  ; 等于的个数
        mov bl,0  ; 大于的个数
        mov cx,8
        mov si,0
  s:    cmp byte ptr ds:[si],18
        je eqq  ; 等于
        ja grr  ; 大于
        inc al
        jmp short nee
  eqq:  inc ah
        jmp short nee
  grr:  inc bl
  nee:  inc si
        loop s

        ; 存储结果
        mov ds:[si],al
        mov ds:[si+1],ah
        mov ds:[si+2],bl

        ; 程序退出
        mov ax,4c00h
        int 21h
codesg ends
end main

5.14 DF标志和串传送指令

【DF-方向标志位(Direction Flag)】
功能:在串处理指令中,控制每次操作后si,di的增减。DF=0则si、di自增;DF=1则si、di自减。

设置指令:

  • cld指令:令DF=0(clear),也就是 si、di 自增。
  • std指令:令DF=1(setup),也就是 si、di 自减。

串传送指令:

  • movsb:按字节传送(8bit),首先将内存单元 ds:[si] 中的内容复制到 es:[di],然后根据DF更新 si、di。
  • movsw:按字传送(16bit),首先将内存单元 ds:[si] 中的内容复制到 es:[di],然后根据DF更新 si、di。

rep指令】
功能:根据cx的值,重复执行后面的指令。经常和串传送指令搭配使用,实现批量的数据传送。下面的用法等价。

最后来看一个 rep和 串传送指令 配合使用的示例,可以看到代码非常简洁!!

【代码示例】用串传送指令,将数据段的前16个字符复制到后16个字符的位置。可以和“4.3节”的代码示例对比。

assume ds:datasg,cs:codesg

datasg segment
        db 'Welcome to masm!'
        db 16 dup(0)
datasg ends

codesg segment
main: ; 数据段初始化
      mov ax,datasg
      mov ds,ax
      ; 复制数据
      mov es,ax
      mov si,0
      mov di,16
      mov cx,16
      rep movsb  ; 循环的复制数据
      ; 程序退出
      mov ax,4c00h
      int 21h
codesg ends
end main

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1698435.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

postgresql|数据库|闪回插件e-maj的部署和使用

前言&#xff1a; E-Maj 是 PostgreSQL 数据库的一个扩展插件&#xff0c;它的全称为 "Elementary Majordomo"。这个扩展的主要功能是为数据库中的表集提供细粒度的写入日志记录和时间旅行能力。这意味着使用 E-Maj 的用户可以在数据库的特定子集上实现事务的回滚&a…

python列表元素的增减之道:删除篇

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、前言 二、删除元素的基本方法 1. 使用remove()方法 2. 使用pop()方法 3. 使用del语句…

mars3d的V2版本的Video2D与V3版本的Video2D实现数据快速迁移

场景&#xff1a; 目前是v2和v3的两个相机视角的不同格式&#xff0c;在Mars3d的V2的旧数据想可以快速迁移到V3版本。 V2版本的数据&#xff1a; {"camera": {"fov": 1.0471975511965976,"dis": 20,"stRotation": 0,"showFrust…

第 33 次CCF认证

1. 词频统计 题目描述 样例输入 代码 #include <bits/stdc.h>using namespace std;int main() {int n,m;cin>>n>>m;vector<int> ans1(m,0),ans2(m,0);while (n --) {int t;cin>>t;vector<int> vis(m1,0);for (int i 1;i < t;i ) {i…

这样的直男程序员,活该你单身一万年!

#分享下相亲时遇到过哪些奇葩现象# 这样的直男程序员&#xff0c;活该你单身一万年&#xff01; 在丛丛脱单小程序上相亲&#xff0c;遇到一个程序员妹纸&#xff0c;于是有了如下的真实故事&#xff1a; 妹子说她是程序员来着&#xff0c;想着我也是程序员&#xff0c;就想交…

【HMGD】STM32/GD32 CAN通信

各种通信协议速度分析 协议最高速度(btis/s)I2C400KCAN1MCAN-FD5M48510MSPI36M CAN协议图和通信帧 CubeMX CAN配置说明 CAN通信波特率 APB1频率 / 分频系数 /&#xff08;BS1 BS2 同步通信段&#xff09;* 1000 ​ 42 / 1 / (111) * 1000 ​ 14,000 KHz ​ 1400000…

1.4 Mac 电脑 Clion 安装教程

目录 1 安装 2 激活 3 汉化 1 安装 去 https://www.jetbrains.com/clion/download/other.html 下载: 也可以直接到链接进行下载:https

DOS学习-目录与文件应用操作经典案例-comp

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一.前言 二.使用 三.案例 案例 1: 基本比较 案例 2: 十进制显示差异 案例 3: 字符形式显…

1-Django开端--学生管理系统

目录 项目结构 前端页面: add_data.html class_data.html index.html apps.py models.py views.py settings,py urls.py ...实现简略的身架... 项目结构 前端页面: add_data.html --添加数据. {% extends index/index.html %}{% block content %} <div class&qu…

基于机器学习的一线城市租房价格预测分析与实现,实现三种算法预测

本文旨在基于机器学习方法&#xff0c;对一线城市租房价格进行预测分析&#xff0c;并使用Matplotlib可视化、随机森林、一元线性回归和多元线性模型进行模型对比。通过爬取北京链家二手房数据作为研究对象&#xff0c;探讨了租房价格与各种因素之间的关系&#xff0c;阐述了研…

实时计算及异构计算随笔笔记

3、异构计算的典型应用 异构计算并不神秘&#xff0c;目前已渗透各个领域&#xff0c;不仅是PC领域&#xff0c;也包括了手持移动设备领域、行业领域&#xff0c;甚至是云计算、分布式计算领域。事实上&#xff0c;异构计算至少在应用端&#xff08;前台&#xff09;并不像它的…

【java程序设计期末复习】chapter4 类和对象

类和对象 编程语言的几个发展阶段 &#xff08;1&#xff09;面向机器语言 计算机处理信息的早期语言是所谓的机器语言&#xff0c;使用机器语言进行程序设计需要面向机器来编写代码&#xff0c;即需要针对不同的机器编写诸如0101 1100这样的指令序列。 &#xff08;2&#x…

【XSS CSRF 】访问时篡改密码——以DVWA-High为例

【XSS & CSRF 】泄露cookie——以DVWA-High为例-CSDN博客第一阶段 目录 前言 一、场景想定 二、过程步骤 1.High等级下的CSRF利用 2.XSSCSRF实现页面访问后密码被修改 三、最终利用——cookie可变下的admin密码修改 1.cookie可变 2.利用过程 总结 前言 第二阶段…

编写子函数+最大公约数和最小公倍数

目录 计算级数和 判断并找出非素数 主函数操作流程 求最大公约数和最小公倍数 编写子函数&#xff0c;该函数的功能是是计算下列级数和&#xff0c;并将和值返回主调函数输出。数据由主函数输入。 fun 函数 sum 函数 main 函数 注意事项 编写函数&#xff0c;该函数的…

C语言——malloc和free用法和常见误区

最近写了个关于动态数组的代码&#xff0c;遇到了一个大坑&#xff0c;特此记录 先说结论&#xff1a; 1.利用malloc创建堆空间&#xff0c;大小最好设置大一点&#xff0c;不然后面存进去的值需要的空间过大会导致各种的堆、指针问题 2.只能使用realloc对已经创建的空间进行修…

自定义类型:结构体详解

1.结构体 1.1 结构的基础知识 结构是一些值的集合&#xff0c;这些值称为成员变量。一个整型数组&#xff0c;它的每个数组元素只能是整型&#xff0c;字符型的数组它的每个元素只能是字符型。但是结构体的每个成员可以是各种不同类型的变量。 1.2结构的声明 //声明 struct t…

视频拼接融合产品的产品与架构设计(四)分布式GPU运算合并单元

上一篇如下 视频拼接融合产品的产品与架构设计(三&#xff09;内存和显存单元数据迁移 视频合并单元说明 对下面这张图做些说明&#xff0c;视频接入是比较常见&#xff0c;可以说是普通&#xff0c;但是做到接入后随即进行比较重的算法运算&#xff0c;这个在视频领域并不多…

海外抖音TK自动挂机,手机全自动挂机,每天轻松搞2张

海外抖音TK自动挂机&#xff0c;手机全自动挂机&#xff0c;每天轻松搞2张 课程获取方式&#xff1a; https://zzmbk.com/

linux系统部署Oracle11g:netca成功启动后1521端口未能启动问题

一、问题描述 执行netca命令&#xff0c;进入图形化界面&#xff0c;进行Oracle端口监听设置 #终端输入命令 netca 最终提示设置成功&#xff1a; 但是我们进行下一步“创建数据库”的时候会报错&#xff0c;说数据库端口1521未开启&#xff01; 二、问题处理 使用命令查看开…

ubuntu安装samba实现共享文件windows可查看ubuntu中的文件

samba的作用&#xff1a;实现共享linux/ubuntu系统中的文件&#xff0c;在windows直接查看操作ubuntu/linux中的文件、文件夹 1、安装samba sudo apt-get install samba如果不能安装samba&#xff0c;则更新apt-get sudo apt-get upgrade sudo apt-get update sudo apt-get d…