首页 > 汇编语言 > 数据操作相关运算符和指令
汇编语言JMP和LOOP(转移)指令
默认情况下,CPU 是顺序加载并执行程序。但是,当前指令有可能是有条件的,也就是说,它按照 CPU 状态标志(零标志、符号标志、进位标志等)的值,把控制转向程序中的新位置。
汇编语言程序使用条件指令来实现如 IF 语句的高级语句与循环。每条条件指令都包含了一个可能的转向不同内存地址的转移(跳转)。控制转移,或分支,是一种改变语句执行顺序的方法,它有两种基本类型:
JMP 指令提供了一种简单的方法来创建循环,即跳转到循环开始时的标号:
如果 ECX 不等于 0,则跳转到由目标给岀的标号。否则,如果 ECX 等于 0,则不发生跳转,并将控制传递到循环后面的指令。
实地址模式中,CX 是 LOOP 指令的默认循环计数器。同时,LOOPD 指令使用 ECX 为循环计数器,LOOPW 指令使用 CX 为循环计数器。
下面的例子中,每次循环是将 AX 加 1。当循环结束时,AX=5, ECX=0:
有时,可能会创建一个太大的循环,以至于超过了 LOOP 指令允许的相对跳转范围。下面给出是 MASM 产生的一条错误信息,其原因就是 LOOP 指令的跳转目标太远了:
在内存窗口上端的 Address 栏里, 键入 & 符号和数组名称,然后点击 Enter。比如,&myArray 就是一个有效的地址表达式。内存窗口将显示从这个数组地址开始的内存块,如下图所示。
如果数组的值是双字,可以在内存窗口中,点击右键并在弹出菜单里选择 4-byte integer。还有不同的格式可供选择,包括 Hexadecimal Display,Signed Display(有符号显示),和 Unsigned Display(无符号显示)。下图显示了所有的选项。
步骤 1 到步骤 3 可以按照任何顺序执行。下面的短程序实现对一个 16 位整数数组求和。
现在看看在汇编语言中如何实现这种操作,用循环来复制一个字符串,而字符串表示为带有一个空终止值的字节数组。变址寻址很适合于这种操作,因为可以用同一个变址寄存器来引用两个字符串。目标字符串必须有足够的空间来接收 被复制的字符,包括最后的空字节:
汇编语言程序使用条件指令来实现如 IF 语句的高级语句与循环。每条条件指令都包含了一个可能的转向不同内存地址的转移(跳转)。控制转移,或分支,是一种改变语句执行顺序的方法,它有两种基本类型:
- 无条件转移:无论什么情况都会转移到新地址。新地址加载到指令指针寄存器,使得程序在新地址进行执行。JMP 指令实现这种转移。
- 条件转移:满足某种条件,则程序出现分支。各种条件转移指令还可以组合起来,形成条件逻辑结构。CPU 基于 ECX 和标志寄存器的内容来解释真 / 假条件。
JMP 指令
JMP 指令无条件跳转到目标地址,该地址用代码标号来标识,并被汇编器转换为偏移 量。语法如下所示:JMP destination当 CPU 执行一个无条件转移时,目标地址的偏移量被送入指令指针寄存器,从而导致迈从新地址开始继续执行。
JMP 指令提供了一种简单的方法来创建循环,即跳转到循环开始时的标号:
top: . . jmp top ;不断地循环JMP 是无条件的,因此循环会无休止地进行下去,除非找到其他方法退岀循环。
LOOP 指令
LOOP 指令,正式称为按照 ECX 计数器循环,将程序块重复特定次数。ECX 自动成为计数器,每循环一次计数值减 1。语法如下所示:LOOP destination循环目标必须距离当前地址计数器 -128 到 +127 字节范围内。LOOP 指令的执行有两个步骤:
- 第一步,ECX 减 1。
- 第二步,将 ECX 与 0 比较。
如果 ECX 不等于 0,则跳转到由目标给岀的标号。否则,如果 ECX 等于 0,则不发生跳转,并将控制传递到循环后面的指令。
实地址模式中,CX 是 LOOP 指令的默认循环计数器。同时,LOOPD 指令使用 ECX 为循环计数器,LOOPW 指令使用 CX 为循环计数器。
下面的例子中,每次循环是将 AX 加 1。当循环结束时,AX=5, ECX=0:
mov ax,0 mov ecx,5 L1: inc ax loop L1一个常见的编程错误是,在循环开始之前,无意间将 ECX 初始化为 0。如果执行了这个操作,LOOP 指令将 ECX 减 1 后,其值就为 FFFFFFFFh,那么循环次数就变成了 4 294 967 296!如果计数器是 CX (实地址模式下),那么循环次数就为 65 536。
有时,可能会创建一个太大的循环,以至于超过了 LOOP 指令允许的相对跳转范围。下面给出是 MASM 产生的一条错误信息,其原因就是 LOOP 指令的跳转目标太远了:
error A2075: jump destination too far : by 14 byte(s)
基本上,在一个循环中不用显式的修改 ECX,否则,LOOP 指令可能无法正常工作。下例中,每次循环 ECX 加 1。这样 ECX 的值永远不能到 0,因此循环也永远不会停止:top: . . inc ecx loop top如果需要在循环中修改 ECX,可以在循环开始时,将 ECX 的值保存在变量中,再在 LOOP 指令之前恢复被保存的计数值:
.data count DWORD ? .code mov ecx, 100 ;设置循环计数值 top: mov count, ecx ;保存计数值 . mov ecx, 20 ;修改 ECX . mov ecx, count ;恢复计数值 loop top
循环嵌套
当在一个循环中再创建一个循环时,就必须特别考虑外层循环的计数器 ECX,可以将它保存在一个变量中:.data count DWORD ? .code mov ecx, 100 ;设置外层循环计数值 L1: mov count, ecx ;保存外层循环计数值 mov ecx, 20 ;设置内层循环计数值 L2 : loop L2 ;重复内层循环 mov ecx, count ;恢复外层循环计数值 loop L1 ;重复外层循环
提示:作为一般规则,多于两重的循环嵌套难以编写。如果使用的算法需要多重循环,则将一些内层循环用子程序来实现。
在 Visual Studio 调试器中显示数组
在调试期间,如果想要显示数组的内容,步骤如下:选择 Debug 菜单 -> 选择 Windows -> 选择 Memory -> 选择Memory 1。则出现内存窗口,可以用鼠标拖动并停靠在 Visual Studio 工作区的任何一边。还可以右键点击该窗口的标题栏,表明要这个窗口浮动在编辑窗口之上。在内存窗口上端的 Address 栏里, 键入 & 符号和数组名称,然后点击 Enter。比如,&myArray 就是一个有效的地址表达式。内存窗口将显示从这个数组地址开始的内存块,如下图所示。
如果数组的值是双字,可以在内存窗口中,点击右键并在弹出菜单里选择 4-byte integer。还有不同的格式可供选择,包括 Hexadecimal Display,Signed Display(有符号显示),和 Unsigned Display(无符号显示)。下图显示了所有的选项。
整数数组求和
在刚开始编程时,几乎没有任务比计算数组元素总和更常见了。汇编语言实现数组求和步骤如下:- 指定一个寄存器作变址操作数,存放数组地址。
- 循环计数器初始化为数组的长度。
- 指定一个寄存器存放累积和数,并赋值为0。
- 创建标号来标记循环开始的地方。
- 在循环体内,将和数与一个数组元素相加。
- 指向下一个数组元素。
- 用LOOP指令重复循环。
步骤 1 到步骤 3 可以按照任何顺序执行。下面的短程序实现对一个 16 位整数数组求和。
; 数组求和(SumArray. asm) .386 .model flat,stdcall .stack 4096 ExitProcess proto,dwExitCode:dword .data intarray DWORD 10000h,20000h,30000h,40000h .code main PROC mov edi, OFFSET intarray ; 1: EDI=intarray 地址 mov ecx, LENGTHOF intarray ; 2 :循环计数器初始化 mov eax,0 ; 3: sum=0 L1: ; 4:标记循环开始的地方 add eax, [edi] ; 5:加一个整数 add edi, TYPE intarray ; 6:指向下一个元素 loop L1 ; 7:重复,直到 ECX=0 invoke ExitProcess, 0 main ENDP END main
复制字符串
程序常常要将大块数据从一个位置复制到另一个位置。这些数据可能是数组或字符串,但是它们可以包括任何类型的对象。现在看看在汇编语言中如何实现这种操作,用循环来复制一个字符串,而字符串表示为带有一个空终止值的字节数组。变址寻址很适合于这种操作,因为可以用同一个变址寄存器来引用两个字符串。目标字符串必须有足够的空间来接收 被复制的字符,包括最后的空字节:
;复制字符串 (CopyStr.asm) .386 .model flat,stdcall .stack 4096 ExitProcess proto,dwExitCode:dword .data source BYTE "This is the source string", 0 target BYTE SIZEOF source DUP(0) .code main PROC mov esi, 0 ;变址寄存器 mov ecx, SIZEOF source ;循环计数器 L1: ;从源字符串获取一个字符 mov al, source [esi] ;保存到目标字符串 mov target [esi] , al ;指向下一个字符 inc esi ;重复,直到整个字符串完成 loop L1 invoke ExitProcess,0 main ENDP END mainMOV 指令不能同时有两个内存操作数,所以,每个源字符串字符送入 AL,然后再从 AL 送入目标字符串。
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算