1 assume cs:codesgView Code 进行编译,连接之后,使用debug来调试这个程序。对于编译器而言,[0]的中括号是不存在的,所以会出现把0放到al中,把1放到bl中。所以,我们要想偏移地址为某个数,所对应的内存单元传递到寄存器当中,就需要进行两步操作: mov bx,0 mov ax,[bx] 这样做的话,就可以实现将偏移地址为0对应的内存单元的值传递给寄存器了。 2、loop 这是一个做循环操作的指令,指令格式:loop 标号。 CPU执行loop指令时,要进行两个步骤的操作: (1)(cx)=(cx)-1(这里的cx放在一个括号里面,意思是cx的值) (2)判断cx是不是为零,不为零的话转到标号出执行程序。 所以,loop和cx是有直接联系的,即cx的值影响着loop的执行结果。所以cx的值也就是指的是loop的循环次数,我们可以想到高级语言中,使用循环时(比如for循环),我们都会定义一个变量,来作为循环的条件,这个变量的值也就是循环的次数。 现在我们来看用汇编来计算2^2结果的代码:
2 codesg segment
3 start: mov ax,2000H
4 mov ds,ax
5 mov al,[0]
6 mov bl,[1]
7 mov cl,[2]
8 mov dl,[3]
9 mov ax,4C00H
10 int 21H
11 codesg ends
12 end start
1 assume cs:codeView Code 计算2^3也是类似的,代码如下:
2 code segment
3 mov ax,2
4 add ax,ax
5
6 mov ax,4c00h
7 int 21h
8 code ends
9 end
1 assume cs:codeView Code 那么计算2^12,我们就要使用循环来做了,不然重复的代码太多,这个时候我们就要使用到loop指令了,代码如下:
2 code segment
3 mov ax,2
4 add ax,ax
5 add ax,ax
6
7 mov ax,4c00h
8 int 21h
9 code ends
10 end
11
1 assume cs:codeView Code 上面的代码中,s就是标号,这个标号可以是任何你自己定义的内容,实际上s标明了一个地址,这个地址处有一个指令:add ax,ax。cx我们定义的值为11,因为要算2^12,所以要循环11次。循环时,cx会减1,循环一次减一次,直到cx为0,停止循环。我们可以使用debug调试编译连接好的程序: 从上面的内容,我们可以得到cx和loop指令互相配合的三个关键点: (1)、在cx中存放循环次数 (2)、loop指令标号所标识的地址要在前面 (3)、要循环执行的程序段,写在标号和loop指令之间。 3、loop和[bx]的联合使用 我们先从几个问题入手:计算ffff:0~ffff:b单元中的数据的和,结果存储在dx中。 (1)、运算后的结果是否会超出dx所能存储的值的范围? ffff:0~ffff:b内存单元中的数据是字节型数据,范围是0~256,12个这样的数据相加,结果是不会比65535更大的,因此dx中是可以存储下的。 (2)、能够将ffff:0~ffff:b直接累加到dx当中呢? 不行,因为ffff:0~ffff:b中的数据是8位的,不能直接加到16位寄存器dx中。 (3)、能否将ffff:0~ffff:b中的数据累加到dl中,并设置(dh)=0,从而实现累加到dx中呢? 不行,dl是一个8位寄存器,能容纳的数据的范围在0~255之间,ffff:0~ffff:b中的数据也是8位的,将12个8位的数据累加到一个8位寄存器中,可能会出现进位丢失的情况。 (4)、那么到底应该怎样将这段内存单元中的数据的和存储到dx中呢? 就是使用一个16位寄存器来做中介,将内存单元中的8位数据赋值到一个16位寄存器ax中,再将这个ax中的数据加到dx上,从而使得运算对象的类型能够的到匹配(ax,dx都是16进制)并且结果不会越界。 因为这里要进行12次的相加,因此要使用到loop指令进行循环相加的操作,具体代码如下:
2 code segment
3 mov ax,2
4 mov cx,11
5 s:add ax,ax
6 loop s
7
8 mov ax,4c00h
9 int 21h
10 code ends
11 end
1 assume cs:codesegView Code 4、Debug的G命令和P命令 当我们对程序进行调试时,循环的次数很大时,我们不可能一次一次的执行t命令,因此要使用G命令或者P命令。G 循环结束的偏移地址,这样就可以一次执行完循环的内容。P,直接执行完循环的内容。 5、一段安全的空间 当我们要直接向内存中写入内容时,这段内存空间不应该存放系统或者其它程序的数据或者代码,否则写入会导致操作系统发生错误, DOS方式下,一般情况,0:200~0:2FF空间汇中没有存放系统或者其它程序的数据或者代码,以后我们写入内存就直接写到这段空间当中。 6、段前缀 指令 mov ax,[bx]中,内存单元的偏移地址由bx给出,而段地址默认在ds中,我们可以在访问内存单元的指令中,显示的给出内存单元的段地址所在的段寄存器,比如,mov ax,ds:[bx],这里的段寄存器可以是ds,cs,es,ss,这些指令中的段寄存器在汇编语言中也被称为段前缀。 7、段前缀的使用 首先我们考虑一个问题:将ffff:0~ffff:b中的数据复制到0:200~0:20b单元中。 分析: (1)0:200~0:20b也就是0020:0~0020:b,它们描述的是同一个内存空间。 (2)在循环中,源始单元 ffff:X 和目标单元 0200:X 的偏移地址X是变量,我们使用一个寄存器bx来存放 代码如下:
2 codeseg segment
3
4 mov ax,0ffffh
5 mov ds,ax ;初始化ds:bx 指向ffff:0内存单元
6 mov bx,0
7
8 mov dx,0 ;初始化累加寄存器dx
9 mov cx,12 ;初始化循环计数寄存器cx
10
11 s: mov al,[bx]
12 mov ah,0
13 add dx,ax ;间接向dx中加上((ds)*16+(bx))单元中的数值
14 inc bx ;ds:bx指向下一个单元
15 loop s
16
17 mov ax,4c00h
18 int 21h
19
20 codeseg ends
21 end
1 assume cs:codesegView Code 当然,我们上面的代码还有需要改进的地方,从上面的代码可以看到,我们重复设置了两次ds的值,这样做没有错,只是在循环的次数比较小的情况下,一旦循环的次数增到很大时,CPU就要进行很多次重复的操作,这样效率也就比较低了。因此,我们可以对代码进行相应的改进:
2 codeseg segment
3
4 mov bx,0 ;偏移地址从0开始
5 mov cx,12 ;循环次数为12次
6
7 s: mov ax,0ffffh
8 mov ds,ax ;ds的值为0ffffh
9 mov dl,[bx] ;(dl)=((ds)*16+bx)将ffff:bx中的数据传入到dl中
10
11 mov ax,0020h
12 mov ds,ax ;ds的值为0020h
13 mov [bx],dl ;((ds)*16+bx)=(dl)将dl中的数据传入到0020:bx中
14
15 inc bx ;bx自增1
16 loop s
17
18 mov ax,4c00h
19 int 21h
20
21 codeseg ends
22 end
1 assume cs:codesegView Code
2 codeseg segment
3 mov ax,0ffffh
4 mov ds,ax ;(ds)=0ffffh
5
6 mov ax,0020h
7 mov es,ax ;(es)=0020h
8
9 mov bx,0 ;(bx)=0,这个时候,ds:bx指向ffff:0,es:bx指0020:0
10 mov cx,12
11
12 s:
13 mov dl,[bx] ;(dl)=((ds)*16+(bx)),将ffff:bx中的数据传入到dl
14 mov es:[bx],dl ;((es)*16+(bx))=(dl),将dl中的数据传入0020:bx
15
16 inc bx
17 loop s
18
19 mov ax,4c00h
20 int 21h
21
22 codeseg ends
23 end