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)
程序的入口处便不再是我们希望执行的指令,所以应当在源程序中指明程序的入口所在,以start 标号标识,并在end后面添加start标号。end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。例如用end指令指明了程序的入口在标号start处<br />那么,根据什么设置CS:IP指向程序的第一条要执行的指令? <br />这一点,是由可执行文件中的描述信息指明的。用伪指令end描述了程序的结束和程序的入口。在编译连接后,由“end start”指明的程序入口,被转化为一个入口地址,存储在可执行文件的描述信息中。当程序被加载入内存之后,加载者从程序的可执行文件的描述信息中读到程序的入口地址,设置CS:IP,这样CPU就从我们希望的地址处开始执行<br />归根结底,我们若要CPU从何处开始执行程序,只要在源程序中用“end 标号”指明就可以了
<a name="5lfmh"></a>
## 在代码段中使用栈
利用栈,将程序中定义的数据逆序存放<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
mov bx,0
mov cx,8
s: push cs:[bx] add bx,2 loop s
mov bx,0
mov cx,8
s0: pop cs:[bx] add bx,2 loop s0
mov ax,4c00h
int 21h
codesg ends end start
我们在描述dw作用时,可以说用它定义数据,也可以说用它来开辟内存空间
<a name="OUgv9"></a>
## 将数据、代码、栈放入不同的段
前面的程序将数据、代码、栈放到一个段里面。有两个问题
- 程序混乱
- 如果数据、栈、代码需要空间超过64KB,就不能放在一个段中(8086CPU)
怎么做? 用定义代码段一样的方法来定义多个段
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
mov ax,data
mov ds,ax ;ds指向data段
mov bx,0 ;ds:bx 指向data段中的第一个单元
mov cx,8
s: push [bx] add bx,2 loop s
mov bx,0
mov cx,8
s0: pop [bx] add bx,2 loop s0
mov ax,4c00h
int 21h
code ends end start
段名就相当于一个标号,它代表了段地址,所以指令“mov ax,data”的含义就是将名称为"data"的段地址送入ax, **一个段中的数据的段地址可由段名代表** ,偏移地址就要看它在段中的位置了。<br />注意:指令"mov ds,data" 是错误的,8086CPU不允许将一个数值直接送入段寄存器中,程序中对段名的引用将被编译器处理为一个表示段地址的数值
<a name="SaYY0"></a>
# and 和 or 指令
<a name="zGM94"></a>
## and 指令
逻辑与运算,按位进行与运算<br />mov al,01100011B<br />and al,00111011B
**通过该指令可将操作对象相应位设为0,其他位不变** <br />例,将al第6位设为0:and al,10111111B
<a name="pyhk0"></a>
## or 指令
逻辑或指令,按位进行或运算<br />mov al,01100011B<br />or al,00111011B
**通过该指令可将操作对象相应位设为1,其他位不变** <br />例,将al的第6位设为1:or al,01000000B
<a name="etE5T"></a>
## 以字符形式给出的数据
在汇编程序中,用'.....' 方式指明数据时是以字符的形式给出的,编译器将把它们转化为相应的ASCII码
例,db 'unIX' 这相当于 db 75H,6EH,49H,58H<br />mov al,'a' 相当于mov al,61H
<a name="KZ97i"></a>
# 更加灵活的内存寻址方式
<a name="VqjP1"></a>
## [bx+idata]
这表示一个内存单元,偏移地址为(bx)+idata bx中的数值加上idata,例如mov ax,[bx+200]<br />亦可写作 0[bx] 5[bx]
<a name="LFDRq"></a>
## SI DI
si di 是8086CPU和bx功能相近的寄存器,si和di不能够分成两个8位寄存器来使用<br />mov si,0<br />mov ax,[si]
mov si,0<br />mov ax,[si+123]
<a name="RiXtk"></a>
## [bx+si] 和 [bx+di]
这表示一个内存单元,偏移地址为(bx)+(si)
mov ax,[bx]+[si]<br />亦可写作<br />mov ax,[bx][si]
<a name="lMur1"></a>
## [bx+si+idata] 和 [bx+di+idata]
表示一个内存单元,偏移地址为(bx)+(si)+idata
mov ax,[bx+si+idata]
<a name="rUfMi"></a>
## 演练
<a name="OiuG1"></a>
### 1
将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
mov cx,6
s: mov al,[bx+3] and al,11011111B mov [bx+3].al add bx,16 loop s
mov ax,4c00H
int 21h
codesg ends end start
在datasg中定义了6个字符串,每个长度为16个字节,它们是连续存放的,可以将这6个字符串看成一个6行16列的二维数组
<a name="F7THk"></a>
### 2
将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
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
add bx,16
mov cx,dx ; 用dx中存放的外层循环的计数值恢复cx
loop s0
mov ax,4c00h
int 21h
codesg ends end start
cx的使用,进行二重循环,若只用一个循环计数器,会造成在进行内层循环的时候,覆盖了外层循环的循环技术值。<br />因此,应当在每次开始内层循环的时候,将外层循环的cx中的数值保存起来,在执行外层循环的loop指令前,再恢复外层循环的cx数值,可以用寄存器dx来临时保存cx中的数值
程序中经常需要进行数据的暂存,可以用寄存器暂存,如上所示,但这不是一个一般化解决方案,因为寄存器数量有限。还可以使用的是内存,可以考虑将需要暂存的数据放到内存单元中,需要使用的时候,再从内存单元中恢复,这样我们就需要开辟一段内存空间
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
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
add bx,16
mov cx,ds:[40H] ; 用dx中存放的外层循环的计数值恢复cx
loop s0
mov ax,4c00h
int 21h
codesg ends end start
上面的程序用内存单元来保存数据,可是做法有些麻烦,如果需要保存多个数据,程序容易混乱<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
mov ax,datasg
mov ds,ax
mov bx,0
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
add bx,16
pop cx ; 从栈顶弹出原cx的值,恢复cx
loop s0
mov ax,4c00h
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