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

5. 流程转移与子程序

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


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标志和串传送指令-movsb/movsw

【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
  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虎慕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值