loop 指令

CPU执行loop指令时,要进行两步操作:

  • (cx)= (cx)-1
  • 判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行

cx中存放循环次数
例:
assume cs:code
code segment
mov ax,2
mov cx,11

s: add ax,ax
loop s

mov ax,4c00h
int 21h
code ends
end

注:标号s标识了一个地址,这个地址处有一条指令:add ax,ax

一段安全的空间

注:在不确定一段内存空间中是否存放着重要的数据或代码的时候,不能随意向其中写入内容,要使用操作系统给我们分配的空间
一般情况下,DOS和其他合法的程序一般不会使用0:200~0:2ff 的256个字节的空间,所以,使用这段空间是安全的

包含多个段的程序

程序取得所需空间的方法有两种

  • 加载程序时候系统为程序分配
  • 程序在执行的过程中向系统申请(不讨论)

我们若要一个程序在被加载的时候取得所需的空间,则必须要在源程序中做出声明。我们通过 在源程序中定义段来进行内存空间的获取

在代码段中使用数据

我们可以在程序中,定义我们希望处理的数据,这些数据就会被编译、连接程序作为程序的一部分写到可执行文件中,当可执行文件中的程序被加载入内存时,这些数据也同时被加载入内存中。与此同时,我们要处理的数据也就自然而然地获得了存储空间。

  • 计算8个数据的和,结果存在ax寄存器中 ``` assume cs:code code segment dw 0123h,0456h,……. ;”dw” 的含义是定义字型数据 (start:) mov bx,0 mov ax,0

    mov cx,8

s:add ax,cs:[bx] add bx,2 loop s

mov ax,4c00h int 21h

code ends end (start)

  1. 程序的入口处便不再是我们希望执行的指令,所以应当在源程序中指明程序的入口所在,以start 标号标识,并在end后面添加start标号。end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。例如用end指令指明了程序的入口在标号start处<br />那么,根据什么设置CS:IP指向程序的第一条要执行的指令? <br />这一点,是由可执行文件中的描述信息指明的。用伪指令end描述了程序的结束和程序的入口。在编译连接后,由“end start”指明的程序入口,被转化为一个入口地址,存储在可执行文件的描述信息中。当程序被加载入内存之后,加载者从程序的可执行文件的描述信息中读到程序的入口地址,设置CS:IP,这样CPU就从我们希望的地址处开始执行<br />归根结底,我们若要CPU从何处开始执行程序,只要在源程序中用“end 标号”指明就可以了
  2. <a name="5lfmh"></a>
  3. ## 在代码段中使用栈
  4. 利用栈,将程序中定义的数据逆序存放<br />我们需要一段可当作栈的内存空间,可以在程序中通过定义数据来取得一段空间,然后将这段空间当做栈空间来用

assume cs:codesg codesg segment dw 0123h,0456h,0789h,0abch…. ;(八个字型数据) dw 0,0,0,0….. ;(16个字型数据),程序加载后将取得16个字内存空间,后面的程序中将这段空间当做栈使用

start: mov ax,cs mov ss,ax mov sp,30h ;将设置栈顶ss:sp 指向cs:30

  1. mov bx,0
  2. mov cx,8

s: push cs:[bx] add bx,2 loop s

  1. mov bx,0
  2. mov cx,8

s0: pop cs:[bx] add bx,2 loop s0

  1. mov ax,4c00h
  2. int 21h

codesg ends end start

  1. 我们在描述dw作用时,可以说用它定义数据,也可以说用它来开辟内存空间
  2. <a name="OUgv9"></a>
  3. ## 将数据、代码、栈放入不同的段
  4. 前面的程序将数据、代码、栈放到一个段里面。有两个问题
  5. - 程序混乱
  6. - 如果数据、栈、代码需要空间超过64KB,就不能放在一个段中(8086CPU
  7. 怎么做? 用定义代码段一样的方法来定义多个段

assume cs:code,ds:data,ss:stack data segment dw 0123h,0456h….. ;(8) data ends

stack segment dw 0,0,0,0,0,0,0,0….. ;(16) stack ends

code segment start: mov ax,stack mov ss,ax mov sp,20h ;设置栈顶ss:sp 指向stack:20

  1. mov ax,data
  2. mov ds,ax ;ds指向data
  3. mov bx,0 ;ds:bx 指向data段中的第一个单元
  4. mov cx,8

s: push [bx] add bx,2 loop s

  1. mov bx,0
  2. mov cx,8

s0: pop [bx] add bx,2 loop s0

  1. mov ax,4c00h
  2. int 21h

code ends end start

  1. 段名就相当于一个标号,它代表了段地址,所以指令“mov ax,data”的含义就是将名称为"data"的段地址送入ax **一个段中的数据的段地址可由段名代表** ,偏移地址就要看它在段中的位置了。<br />注意:指令"mov ds,data" 是错误的,8086CPU不允许将一个数值直接送入段寄存器中,程序中对段名的引用将被编译器处理为一个表示段地址的数值
  2. <a name="SaYY0"></a>
  3. # and 和 or 指令
  4. <a name="zGM94"></a>
  5. ## and 指令
  6. 逻辑与运算,按位进行与运算<br />mov al,01100011B<br />and al,00111011B
  7. **通过该指令可将操作对象相应位设为0,其他位不变** <br />例,将al6位设为0and al,10111111B
  8. <a name="pyhk0"></a>
  9. ## or 指令
  10. 逻辑或指令,按位进行或运算<br />mov al,01100011B<br />or al,00111011B
  11. **通过该指令可将操作对象相应位设为1,其他位不变** <br />例,将al的第6位设为1or al,01000000B
  12. <a name="etE5T"></a>
  13. ## 以字符形式给出的数据
  14. 在汇编程序中,用'.....' 方式指明数据时是以字符的形式给出的,编译器将把它们转化为相应的ASCII
  15. 例,db 'unIX' 这相当于 db 75H,6EH,49H,58H<br />mov al,'a' 相当于mov al,61H
  16. <a name="KZ97i"></a>
  17. # 更加灵活的内存寻址方式
  18. <a name="VqjP1"></a>
  19. ## [bx+idata]
  20. 这表示一个内存单元,偏移地址为(bx)+idata bx中的数值加上idata,例如mov ax,[bx+200]<br />亦可写作 0[bx] 5[bx]
  21. <a name="LFDRq"></a>
  22. ## SI DI
  23. si di 8086CPUbx功能相近的寄存器,sidi不能够分成两个8位寄存器来使用<br />mov si,0<br />mov ax,[si]
  24. mov si,0<br />mov ax,[si+123]
  25. <a name="RiXtk"></a>
  26. ## [bx+si] 和 [bx+di]
  27. 这表示一个内存单元,偏移地址为(bx)+(si)
  28. mov ax,[bx]+[si]<br />亦可写作<br />mov ax,[bx][si]
  29. <a name="lMur1"></a>
  30. ## [bx+si+idata] 和 [bx+di+idata]
  31. 表示一个内存单元,偏移地址为(bx)+(si)+idata
  32. mov ax,[bx+si+idata]
  33. <a name="rUfMi"></a>
  34. ## 演练
  35. <a name="OiuG1"></a>
  36. ### 1
  37. datasg段中每个单词头一个字母改为大写字母

assume cs:codesg,ds:datasg

datasg segment db ‘1. file ‘ db ‘2. edit ‘ db ‘3. search ‘ db ‘4. view ‘ db ‘5. options ‘ db ‘6. help ‘ datasg ends

codesg segment start:mov ax,datasg mov ds,ax mov bx,0

  1. mov cx,6

s: mov al,[bx+3] and al,11011111B mov [bx+3].al add bx,16 loop s

  1. mov ax,4c00H
  2. int 21h

codesg ends end start

  1. datasg中定义了6个字符串,每个长度为16个字节,它们是连续存放的,可以将这6个字符串看成一个616列的二维数组
  2. <a name="F7THk"></a>
  3. ### 2
  4. datasg段每个单词改为大写字母

assume cs:codesg, ds:datasg

datasg segment db ‘ibm ‘ db ‘dec ‘ db ‘dos ‘ db ‘vax ‘ datasg ends

codesg segment start:mov ax,datasg mov ds,ax mov bx,0

  1. mov cx,4

s0: mov dx,cx ; 将外层循环的cx值保存在dx中 mov si,0 mov cx,3 ; cx设置为内层循环的次数

s: mov al,[bx+si] and al,11011111B mov [bx+si],al inc si loop s

  1. add bx,16
  2. mov cx,dx ; dx中存放的外层循环的计数值恢复cx
  3. loop s0
  4. mov ax,4c00h
  5. int 21h

codesg ends end start

  1. cx的使用,进行二重循环,若只用一个循环计数器,会造成在进行内层循环的时候,覆盖了外层循环的循环技术值。<br />因此,应当在每次开始内层循环的时候,将外层循环的cx中的数值保存起来,在执行外层循环的loop指令前,再恢复外层循环的cx数值,可以用寄存器dx来临时保存cx中的数值
  2. 程序中经常需要进行数据的暂存,可以用寄存器暂存,如上所示,但这不是一个一般化解决方案,因为寄存器数量有限。还可以使用的是内存,可以考虑将需要暂存的数据放到内存单元中,需要使用的时候,再从内存单元中恢复,这样我们就需要开辟一段内存空间

assume cs:codesg, ds:datasg

datasg segment db ‘ibm ‘ db ‘dec ‘ db ‘dos ‘ db ‘vax ‘ dw 0 ; 定义一个字,用来暂存cx datasg ends

codesg segment start:mov ax,datasg mov ds,ax mov bx,0

  1. mov cx,4

s0: mov ds:[40H],cx ; 将外层循环的cx值保存在dx中 mov si,0 mov cx,3 ; cx设置为内层循环的次数

s: mov al,[bx+si] and al,11011111B mov [bx+si],al inc si loop s

  1. add bx,16
  2. mov cx,ds:[40H] ; dx中存放的外层循环的计数值恢复cx
  3. loop s0
  4. mov ax,4c00h
  5. int 21h

codesg ends end start

  1. 上面的程序用内存单元来保存数据,可是做法有些麻烦,如果需要保存多个数据,程序容易混乱<br />**一般来说,在需要暂存数据的时候,我们都应该使用栈**

assume cs:codesg, ds:datasg

datasg segment db ‘ibm ‘ db ‘dec ‘ db ‘dos ‘ db ‘vax ‘ datasg ends

stacksg segment dw 0,0,0,0,0,0,0,0 stacksg ends

codesg segment start:mov ax,stacksg mov ss,ax mov sp,16

  1. mov ax,datasg
  2. mov ds,ax
  3. mov bx,0
  4. mov cx,4

s0: push cx ; 将外层循环的cx值压栈 mov si,0 mov cx,3 ; cx设置为内层循环的次数

s: mov al,[bx+si] and al,11011111B mov [bx+si],al inc si loop s

  1. add bx,16
  2. pop cx ; 从栈顶弹出原cx的值,恢复cx
  3. loop s0
  4. mov ax,4c00h
  5. int 21h

codesg ends end start ```

div 指令

有两个问题

  • 除数:分为8位或16位,在一个reg或内存单元中
  • 被除数:默认放在AX,DX和AX中,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位
  • 结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数。如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数

格式—— div reg 或 div 内存单元
div byte ptr ds:[0]

计算100001/100
分析:被除数大于65535,不能用ax寄存器存放,要用ax和dx两个寄存器联合存放,也就是说要进行16位除法。除数100小于255,可以在8位寄存器中存放,但被除数是32位,要求除数应为16位,所以要用一个16位寄存器存放数100。 100001—-186A1H
mov dx,1
mov ax,86A1H
mov bx,100
div bx
程序执行后,(ax)=03E8H (dx)=1

计算1001/100
mov ax,1001
mov bl,100
div bl

mul 指令

乘法指令,有以下两点注意

  • 两个相乘的数:要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中。如果是16位,一个默认在AX中,另一个放在16位reg或内存字单元中
  • 结果:如果是8位乘法,结果默认放在AX中,如果是16位乘法,结果高位默认在DX中存放,低位在AX中放

伪指令dd,dup

dd

dd是用来定义双字型数据的
比如
data segment
db 1
dw 1
dd 1
data ends

dup

dup是一个操作符,如db dw dd 一样,是由编译器识别处理的符号,和db,dw,dd 等数据定义伪指令配合使用,用来进行数据的重复
如 db 3 dup (0) 定义了3个字节,它们的值都是0
db 3 dup (0,1,2) 定义了9个字节,它们是0,1,2,0,1,2,0,1,2
db 3 dup (‘abc’,’ABC’) 定义了18个字节,它们是’abcABCabcABCabcABC’

dup 是一个十分有用的操作符,比如要定义一个容量为200个字节的栈段—-
stack segment
db 200 dup (0)
stack ends