loop 指令
-
loop指令 和 cx寄存器 配合使用,用于执行循环操作,类似高级语言的for、while、do-while
-
高级语言的 for、while、do-while 底层大部分是通过 loop指令 来实现的(小部分是通过跳转来实现的)
-
使用格式
mov cx, 循环次数 标号: 循环执行的程序代码(可多行) loop 标号
-
loop指令 执行流程
- 步骤1,先将 cx寄存器 的值 -1,即cx = cx - 1;
- 步骤2,判断cx的值:
- 2.1 如果不为零,则执行标号出的代码,执行完成后,跳转到步骤1
- 2.2 如果为零,则执行 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、dw、dup
-
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