loop指令 和 cx寄存器 配合使用,用于执行循环操作,类似高级语言的for、while、do-while
高级语言的 for、while、do-while 底层大部分是通过 loop指令 来实现的(小部分是通过跳转来实现的)
使用格式
mov cx, 循环次数
标号:
循环执行的程序代码(可多行)
loop 标号
loop指令 执行流程
示例代码一
assume cs:code
code segment
; ---------------------- 方式一(使用累加) ----------------------:
; 在汇编语言中,计算 2^6 的代码(仅限于计算2的6次方,计算其他数的次方,不能这样写)
mov ax, 0002H ; ax = 2
mov ax, 0002H ; ax = 2 + 2 = 4
mov ax, 0002H ; ax = 4 + 4 = 8
mov ax, 0002H ; ax = 8 + 8 = 16
mov ax, 0002H ; ax = 16 + 16 = 32
mov ax, 0002H ; ax = 32 + 32 = 64
; ---------------------- 方式二(使用循环) ----------------------
; 在汇编语言中,计算 2^6 的代码(仅限于计算2的6次方,计算其他数的次方,不能这样写)
; 分析:
; 1.代码在运行过程中,先执行一次标号下的循环体(这点类似于do-while)
; 2.读到 (loop 标号) 指令时,将 cx寄存器 的值 -1 后,再赋值给 cx寄存器
; 3.判断cx的值是否为0:不为零,执行循环体; 为零,执行(loop 标号) 指令后面的代码
; 4.所有循环的实现,本质上都是改变 IP指针 的指向。所以,执行loop循环的时候,IP的值会往回跳动
; 5.标号是给编译器看的,可以为每一行汇编指令添加一个标号。标号的作用是给每一句汇编指令起了个名字。loop指令 执行时,会根据标号读取标号对应指令的偏移地址,然后以此偏移地址修改 IP寄存器的值
; 本题中 cx寄存器 的值为5,因为是先减后判断,所以 loop指令 会执行 4 次循环体,加上一开始预先执行的 1 次循环体,总共执行了 5 次。即 5 = 4 + 1
mov ax, 0002H
mov cx, 0005H ; 循环次数,如果填0,则为最大循环次数(32位寄存器和64位寄存器下,约等于无限循环)
looptag: ; 定义标号
add ax, ax
loop looptag ; 执行循环
; ---------------------- 扩展一 ----------------------
; 如果将方式二修改成以下形式,则为死循环
mov ax, 0002H
mov cx, 0005H ; 循环次数
looptag: ; 定义标号
add ax, ax
mov cx, 0005H
loop looptag ; 执行循环
; ---------------------- 扩展二 ----------------------
; 如果将方式二修改成以下形式,则为死循环
s0: mov ax, 0002H
s1: mov cx, 0005H
s2: add ax, ax
loop s0
; ---------------------- 扩展三 ----------------------
; 在计算机底层,是没有负数概念的,比如在 8086CPU 中:
mov ax, 0000H
sub ax, 1 ; 此时 ax寄存器里面的值为 ffffH
; ---------------------- 退出程序 ----------------------
mov ax, 4c00H
int 21H
code ends
end
示例代码二
; 求以下几个字节中数据的和(注意这里求的是单个字节)
; FFFF0 -- ffH
; FFFF1 -- ffH
; FFFF2 -- ffH
;---------------------- 方式一 ----------------------
; 配置数据段
mov ax, ffffH
mov ds, ax
; 将 dx 清零
mov dx, 0000H
mov ax, 0000H
mov al, [0]
add dx, ax
mov ax, 0000H
mov al, [1]
add dx, ax
mov ax, 0000H
mov al, [2]
add dx, ax
;---------------------- 方式二 ----------------------
; 配置数据段
mov ax, ffffH
mov ds, ax
; 将 bx 和 dx 清零
mov bx, 0000H
mov dx, 0000H
mov cx, 0003H
s: mov ax, 0000H
mov al, [bx]
add dx, ax
add bx, 0001H
loop s
补充:
1.获取数据,除了通过 ds段 来获取,还可以利用其他 段寄存器 来获取
mov ax, ds:[0]
mov ax, cs:[0]
mov ax, ss:[0]
mov ax, es:[0]
db(define byte):自定义字节,往内存里面放字节数据。伪指令,被编译器解析
dw(define word):自定义字,往内存里面放字数据。伪指令,被编译器解析
db 和 dw 的使用
assume cs:code
code segment
db 1,2 ; 在名为code的段的[0, 1]位置自定义字节,code[0] = 01H,code[1] = 02H
db 'hello' ; 在名为code的段的[2, 6]位置自定义字节,code[2] = 'h',code[3] = 'e',code[4] = 'l',code[5] = 'l',code[6] = '0'。内存中存放的是字符数据对应的ASCII码值,注意,这里是单引号字符数据
db "hank" ; 在名为code的段的[7, 10]位置自定义字节,code[7] = 'h',code[8] = 'a',code[9] = 'n',code[10] = 'k'。内存中存放的是字符串数据对应的ASCII码值,注意,这里是双引号字符串数据
code_segment_start_tag: ; 代码段开始标记
mov al, cs:[0] ; al = 1
mov ah, 0
; 退出程序
mov ax, 4c00H
int 21H
code ends
end code_segment_start_tag ; 代码段结束标记,这种 IP寄存器为 code_segment_start_tag 标号对应的偏移地址
assume cs:code
code segment
dw 1,2 ; 在名为code的段的[0, 3]位置自定义字节,code[0, 1] = 0001H,code[2, 3] = 0002H
dw 'hello' ; 在名为code的段的[4, 13]位置自定义字节,code[4, 5] = 'h',code[6, 7] = 'e',code[8, 9] = 'l',code[10, 11] = 'l',code[12, 13] = '0'。内存中存放的是字符数据对应的ASCII码值,注意,这里是单引号字符数据
dw "hank" ; 在名为code的段的[14, 21]位置自定义字节,code[14, 15] = 'h',code[16, 17] = 'a',code[18, 19] = 'n',code[20, 21] = 'k'。内存中存放的是字符串数据对应的ASCII码值,注意,这里是双引号字符串数据
code_segment_start_tag: ; 代码段开始标记
mov al, cs:[1] ; 注意:这里al = 00H,因为 cs:[1] 取的是第一个数据的高八位
mov ah, 0
; 退出程序
mov ax, 4c00H
int 21H
code ends
end code_segment_start_tag ; 代码段结束标记
assume cs:code
code segment
db 20 dup(0) ; 在名为code的段的[0, 19]位置自定义字节,每个字节都置为0
db 20 dup(1) ; 在名为code的段的[20, 39]位置自定义字节,每个字节都置为1
dw 20 dup(2) ; 在名为code的段的[40, 79]位置自定义字,每个字都置为2
dw 20 dup(3) ; 在名为code的段的[80, 119]位置自定义字,每个字都置为3
code_segment_start_tag: ; 代码段开始标记
mov al, cs:[0] ; 这里 al = 0
mov al, cs:[20] ; 这里 al = 1
mov al, cs:[40] ; 这里 al = 2
mov al, cs:[80] ; 这里 al = 3
; 退出程序
mov ax, 4c00H
int 21H
code ends
end code_segment_start_tag ; 代码段结束标记
这样定义数据段,栈段,代码段不好,因为数据段、栈段、代码段都定义在同一个地址下
assume cs:code
code segment
; 可以通过自定义数据,在代码段中预留出一块我们想要的内存空间
; 预留出40个字节的内存空间。数据段和栈段共用此内存空间,数据段的内存空间分配为 code[0, 40],栈段内存空间分配为 code[40, 0]
db 40 dup(0)
code_segment_start_tag:
; 注意:因为语法问题,这里不能直接 mov ds, cs 或者 mov ss, cs。即语法不允许将一个段寄存器的值直接送入到另一个段寄存器中
; 定义数据段,整个40个字节,都用作数据段
mov dx, cs
mov ds, dx
mov ax, 1122H
mov [0], ax
; 定义栈段,整个40个字节,都用作栈段,注意:栈顶指针的位置为 40 + 2 = 42
mov dx, cs
mov ss, dx
mov sp, 42
push ax
; 退出程序
mov ax, 4c00H
int 21H
code ends
end code_segment_start_tag
将数据段、栈段、代码段分别定义在不同的地址空间下,是一种较好的编程方式
; 将定义的数据段,栈段,代码段的段地址分别送入数据段寄存器,栈段寄存器,代码段寄存器
assume ds:data, ss:stack, cs:code
; 数据段(存放数据,比如高级语言中的全局变量)
; 数据段要实际放有数据,才会分配DS的值
; 段名 data 在编译成机器码的过程中,会被自动映射成相应的物理地址
data segment
db 20 dup(0)
age dw 20H
data ends
; 栈段(存放数据,比如高级语言中的局部变量)
; 栈段要实际放有数据,才会分配 SS 和 SP 的值
; 段名 stack 在编译成机器码的过程中,会被自动映射成相应的物理地址
stack segment
db 20 dup(0)
stack ends
; 代码段
code segment
code_segment_start_tag:
; 显式设置 数据段寄存器 的值
; mov ax, data
; mov ds, ax
; 显式设置 栈段寄存器 的值
; mov ax, stack
; mov ss, ax
; 修改age的值,方式一
mov ax, 1122H
mov age, ax
; 修改age的值,方式二(因为age在数据段中的偏移地址为20)
mov ax, 1122H
mov [20], ax
; 修改age的值,方式三(因为age在数据段中的偏移地址为14H)
mov ax, 1122H
; mov [14H], ax
; 退出程序
mov ax, 4c00H
int 21H
code ends
end code_segment_start_tag
关于标号的问题
assume ss:stack
stack segment
; 关于标号的问题:
; 2个Byte(1个Word)里面放了20H这个16进制的数据
; 这样写,好像往内存里面放了个20H,然后这个20H叫做age
; 但是实际上,这个20H叫做age,只有编译器知道,在代码编译成机器码的时候,age最终就变成了一个确定的物理地址值
; 即age只是给编译工具看的
; 换句话说,标号只是给编译工具看的,在反汇编的时候,根本看不到age这个变量
; 段名,变量名,都是标号(也都是地址值)
age dw 20H
stack ends
end
打印 hello world!
assume ds:data, ss:stack, cs:code
; 数据段
data segment
db 20 dup(0);
string0 db 'hello world!$' ; 无冒号,这里string0表示的是首地址里面的值,1个字节
string1: db 'hello world!$' ; 有冒号,这里string1表示的是首地址的地址偏移值,2个字节
data ends
; 栈段
stack segment
db 20 dup(0);
stack ends
; 代码段
code segment
code_tag:
; 为保险起见,显式设置一下 ds 和 ss
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
; 业务逻辑代码
; 执行屏幕打印的过程:CPU从内存中读出要显示的数据,将这些数据放到显存里面去
; CPU会取出 ds寄存器 里面的值,当做要打印的字符数据的段地址
; CPU会取出 dx寄存器 里面的值,当做要打印的字符数据的偏移地址
; CPU会从 ds:dx 处开始读取数据,直到遇到 $ 符号结束
; 为 dx寄存器 赋值,方式一(自己算地址偏移量):
mov dx, 14H
; 为 dx寄存器 赋值,方式二(使用标号):
; offset 表示获得标号对应的偏移量
mov dx, offset string0
; 为 dx寄存器 赋值,方式三(使用标号):
mov dx, string1
; 注意:这句代码的意思是将 string0 地址下的数值 赋值给 dx寄存器
; 由于string0 地址下的数值只有1个字节,dx寄存器有2个字节,因此会报错:
; operands do not match: 16 bit register and 8 bit address
; mov dx, string0
; 调用屏幕打印功能
mov ah, 09H
int 21H
; 退出程序
mov ah, 4cH
int 21H
code ends
end code_tag
end