汇编(七)

目录

    • loop 指令
    • db、dw、dup
    • 数据段、栈段、代码段的定义

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 的代码(仅限于计算26次方,计算其他数的次方,不能这样写)
        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 的代码(仅限于计算26次方,计算其他数的次方,不能这样写)   
    	; 分析:  
    	; 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
    

你可能感兴趣的:(汇编基础)