这一章的主题是汇编计算累加和,以及寻址方式的学习。
and是按位与,or是按位或。
两者的目的操作数都必须是8位或者16位的内存单元或者通用寄存器,源操作数必须是与目的操作数同宽度的内存单元、通用寄存器或者立即数。注意,与其他指令一样,源操作数和目的操作数不能都为内存单元。
两者对标志寄存器的影响相同:OF和CF被清零,SF、ZF、PF依计算结果而定,AF位的状态未定义。
计算机中的栈段一般是从高地址向低地址生长,也就是说压栈减地址,出栈加地址。在这里,我们选的栈段初始地址为0x0000,由于计算机内部采用补码运算,所以在压栈第一个元素时,采用0x0000-0x10,即从0xfffe开始存。由于Intel处理器采用小端方式,所以低地址存低字节,即0xfffe存低字节,0xffff存高字节,之后再压栈、出栈都按这个逻辑。
数据段段基地址在DS中保存,附加段段基地址在ES中保存,栈段段基地址在SS中保存。
与push相反,过程是先取数据,再+2。同样不影响任何标志位。
在Bochs中,查看栈信息使用命令“print-stack”,后面可以跟参数指定要打印接下来的多少元素,使用该命令会打印出从SP指向的栈顶向下的一系列元素。但是由于此处计算机不会区分数据区和栈区,而我们的栈高地址处是栈底元素,所以可能会打印出不在栈中的值。
操作数直接位于寄存器中
操作数是个立即数
给出一个内存地址作为偏移地址,以此访问
使用基址寄存器BX或BP来保存偏移地址。这允许在基址寄存器的基础上使用一个偏移量,比如
mov dx, [bp-2]
类似于基址寻址,但是不同的是这里使用的是变址寄存器SI和DI,同样允许偏移量
组合BX、BP和SI、DI,来提供地址,同样允许偏移量。
;以下计算1到100的和
xor ax,ax
mov cx,100
@f:
add ax,cx
loop @f
;修改计算1到1000的和
xor ax,ax
xor dx,dx
mov cx,1000
@f:
add ax,cx
adc dx,0
loop @f
;以下计算累加和的每个数位
xor cx,cx ;设置堆栈段的段基地址
mov ss,cx
mov sp,cx
mov bx,10 ;line 17~line 20还必须得有,不然不行
xor cx,cx
inc cx
div bx
or dl,0x30
push dx
@d: ;如果没有上面那一块,直接这一块计算,原先dx中的高位值就会清零,结果就错了
inc cx ;而如果上面17~20做了,由于除以了10,高位dx中就不会再有非零数据了,只保存每次计算的余数,所以不再会出错
xor dx,dx
div bx
or dl,0x30
push dx
cmp ax,0
jne @d
;以下显示各个数位
@a:
pop dx
mov [es:di],dl
inc di
mov byte [es:di],0x07
inc di
loop @a
jmp near $
times 510-($-$$) db 0
db 0x55,0xaa
adc指令就是一个add指令的扩展,在计算完add之后会把CF位也加上,这样可以使用16位寄存器计算多于16位的数据。比如这里。**注意,因为两个二进制数相加,最多只能进一位,不可能进两位(比较容易证),所以line6~7简单两行代码就能完成多于16位的计算。**因为每次最多进一位,所以直接adc dx,0 即可保存所有高于16位的信息,不用考虑那么多。
相比于源代码,多出来了line17~20,原因注释里说了。由于计算后,dx中保存高16位,ax中保存低16位,而结果是500500,要大于16位的最大值65535,但是在一次÷10之后,50050<65535,所以在低16位,即ax中就能放得开了,dx就可以清零了,用来每次除法保存余数。