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执行内存中某处代码的指令。下面给出转移指令的分类:
- 按转移行为:
- 段内转移:只修改IP,如
jmp ax
。- 段间转移:同时修改CS和IP,如
jmp 1000:0
。
- 根据指令对IP修改的范围不同
- 段内短转移:IP修改范围为-128~127
- 段内近转移:IP修改范围为-32768~32767
- 按转移指令
- 无条件转移指令:如
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
指令的使用总结:
- 【
jmp 标号
】跳转到标号所在的位置。
- 段内短转移:格式为
jmp short 标号
,实现8位补码的相对位移,跳转范围是-128~127。- 段内近转移:格式为
jmp near ptr 标号
,实现16位补码的相对位移,跳转范围是-32768~32767。- 段间转移(远转移):格式为
jmp far ptr 标号
,跳转范围在8位补码范围内给出相对位移,超过8位补码范围后则会直接给出目标地址。
- 【
jmp 寄存器
】令 IP=(寄存器),实现16位的段内位移,比如jmp bx
。- 【
jmp 内存单元
】跳转到的内存单元中给出的地址。
- 段内转移:格式为
jmp word ptr 内存单元地址
,令 IP=(内存单元),比如jmp word ptr [bx]
。- 段间转移:格式为
jmp dword ptr 内存单元地址
,令 CS=(高位字)、IP=(低位字),比如jmp dword ptr [bx]
。
- 【
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。下面是其具体使用格式:
- 【
call 标号
】
- 段内转移:格式是
call 标号
。将 IP 压栈,然后令 (IP) = (IP) + 16位补码位移,跳转范围是-32768~32767,该位移由编译器计算得出。- 段间转移:格式是
call far ptr 标号
。依次将 CS、IP 压栈,并令 CS:IP = 标号对应的段地址:偏移地址。
【
call 寄存器
】将当前IP值压栈,然后令 IP=(寄存器),实现16位的段内跳转。相当于push IP
、jmp 寄存器
。【
call 内存单元
】
- 段内转移:格式是
call word ptr 内存单元地址
。将 IP 压栈,然后令 IP=(内存单元) 实现跳转。- 段间转移:格式是
call dword ptr 内存单元地址
。依次将 CS、IP 压栈,然后令 CS=(高位字)、IP=(低位字)。
ret
/retf
指令的功能是返回,可以单独使用进行流程转移,但一般会和上一个 call
配合使用:
- 段内返回(近转移):格式是
ret
,本质上就是将 IP 出栈,相当于pop IP
。- 段间返回(远转移):格式是
retf
,本质上是依次将 IP、CS 出栈,相当于pop IP
、pop CS
。注:若
ret
/retf
后跟了一个常数 n,表示 栈顶指针寄存器SP+n,表示忽略 n 字节的栈内参数。
5.6 call和ret的配合使用
本小节演示如何成对调用 call
、ret
指令,来实现子程序的调用和返回,注意需要设置栈。
【代码示例】计算 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 ptr
、word ptr
指定乘法位数。使用乘法指令时,切记提前在默认寄存器中设置好“被乘数”,且默认寄存器不作别的用处,四个操作数的说明:
- 乘数:由操作数给出所在位置,若为内存单元必须指出数据长度。
- 被乘数:8位乘法保存在AL;16位乘法保存在AX。
- 结果:8位乘法保存在AX;16位乘法中高16位在DX、低16位在AX。
示例指令 | 被乘数 | 乘数 | 结果 | |
---|---|---|---|---|
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小节中已经演示了如何使用 call
、ret
指令实现子程序的调用和返回,其中参数传递的方法有:
- 用寄存器传递参数:代码最简便,但寄存器数量有限,不能大量存储数据。
- 用内存单元传递参数:需要定义数据段,常用于批量的存储或传递数据。
- 用栈传递参数:需要定义栈段,但注意及时入栈、出栈,不要影响程序的正常退出。
下面使用代码来演示这三种方法的不同:
【代码示例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 ; 存储高16位 mov 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 ; 存储高16位 mov ds:[2],ax ; 存储低16位 ret 4 ; 本质上为 sp+4,相当于a,b出栈 codesg ends end main
注:bp 如果有其他用途,可以先入栈、用完再出栈,只影响栈内的相对索引。
5.9 寄存器冲突的问题-子程序标准框架
上一小节的最后一个代码示例说明了在子程序中调用寄存器可能会与主程序的寄存器产生冲突,所以子程序中一定要将用到的寄存器入栈,子程序退出前出栈。下面是一个主程序和子程序都调用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 寄存器是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。有含义的位如下图所示:
- 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的指令集中,影响标志寄存器的大都是运算指令,进行逻辑或算术运算;比如 add
、sub
、mul
、div
、inc
、or
、and
等。对标志寄存器没有影响的大都是传送指令,比如 mov
、push
、pop
等。使用一条指令的时候,要注意这条指令的全部功能,其中包括执行结果对标记寄存器的哪些标志位造成影响。最后给出直接访问标志寄存器的方法:
pushf
:将标志寄存器的值压栈。popf
:从栈中弹出数据,送入标志寄存器中。- debug模式下,直接使用
r
指令可以查看标志寄存器的值。注:
inc
不会影响进/借位标志位CF。
下面通过一个例子展示标志寄存器的变化:
【代码示例】使用下面的指令后,标志寄存器的变化。
指令 结果 溢出标志
OF符号标志
SF零标志
ZF奇偶标志
PF进位标志
CFsub al,al 0 0/NV 0/PL 1/ZR 1/PE 0/NC mov al,10h 0001_0000b - - - - - add al,90h 1010_0000b 0/NV 1/NG 0/NZ 1/PE 0/NC mov al,80h 1000_0000b - - - - - add al,80h 1_0000_0000b 1/OV 0/PL 1/ZR 1/PE 1/CY mov al,0FCh 1111_1100b - - - - - add al,05h 1_0000_0001b 0/NV 0/PL 0/NZ 0/PO 1/CY mov al,7Dh 0111_1101b - - - - - add al,0Bh 1000_1000b 1/OV 1/NG 0/NZ 1/PE 0/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)≠0 ZF=0 大于等于 (ax)≥(bx) (ax)-(bx)不借位 CF=0 小于等于 (ax)≤(bx) (ax)-(bx)或者借位,或者结果为0 ZF=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)≠0 ZF=0 大于等于 (ax)≥(bx) (ax)-(bx)为非负,且无溢出 OF=0且SF=0 小于等于 (ax)≤(bx) (ax)-(bx)为非负,且有溢出 OF=1或SF=0
【
jxxx 标号
】
功能:条件转移指令,满足条件后就会跳转到标号处。可以根据下面的缩写任意组合。
缩写说明:
类型 指令 跳转条件 标志位 类型 指令 跳转条件 标志位 单个标志位 jz 结果为0 ZF=1 无符号数比较结果 je/jz 相等/结果为0 ZF=1 jnz 结果非0 ZF=0 jne/jnz 不相等/结果非0 ZF=0 js 结果为负 SF=1 jb/jnae/jc 低于/不高于等于/有借位 CF=1 jns 结果非负 SF=O jnb/jae/jnc 不低于/高于等于/无借位 CF=0 jo 结果溢出 OF=1 ja/jnbe 高于/不低于等于 CF=0且ZF=0 jno 结果未溢出 OF=0 jna/jbe 不高于/低于等于 CF=1或ZF=1 jp 奇偶位有效 PF=1 有符号数比较结果 je/jz 相等/结果为0 ZF=1 jnp 奇偶位无效 PF=0 jne/jnz 不相等/结果非0 ZF=0 jc 有借位 CF=1 jl/jnge 低于/不大于等于 SF=1且OF=0 jnc 无借位 CF=0 jnl/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
显然,将 cmp
、jxxx
组合使用,就可以实现高级语言中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