第七章 比高斯更快的计算
7.3 显示字符串
- '1+2+3+…+100='字符串编译阶段,编译器将把它们拆开,形成一个个单独的字节
- jump near start跳过没有指令的数据区
- 11~15行初始化数据段寄存器
DS
和附加段寄存器ES
- 18~28行用于显示字符串,
首先索引寄存器SI指向DS
段内待显示字符串的首地址,即标号"message"所代表的汇编地址
用另一个索引寄存器DI指向ES段内的偏移地址0处,ES是指向0xB800段的 - 字符串的显示依赖循环。如使用loop,需要
CX
寄存器
20行循环次数为字符串长度=start-message
22行,首先从数据段中,逻辑地址为DS:SI
取得第一个字符
将其传送到逻辑地址ES:DI
即显示缓冲区
24行,DI的内容加一,以指向该字符在显示缓冲区内的属性字节,0x07,黑底白字
26,27行,寄存器SI
,DI
加一指向原位置和目标位置的下一个单元
28行,执行循环。先将CX
的内容减一,然后根据CX
是否为零决定是否循环,CX
为零时,显示完毕。
7.4 计算1到100的累加和
AX清零,1传到寄存器CX
,开始累加。CX
同100比较
AX中得到累加和。
7.5 累加和各个数位的分解与显示
栈和栈段的初始化
得到累加和,要把各数位分解出来,并准备在屏幕上显示。
保存在栈中,栈段,由段寄存器SS指向。
需要栈指针寄存器SP
来指示下一个数据应当压入栈内的什么位置,或者数据从哪里出栈
40~42行初始化SS
和SP
的内容
分解各个数位并压栈
44行 除数传送到BX
每次除法结束后,都做一次判断,如果商为0的话,分解过程可以提前结束,必须记住实际有多少个数位。45行CX
寄存器清零,用来累计
47到53行也是循环体,每次执行,分解一个数位。每次分解CX
加一,表明多一个数位
48,49行,将DX
清零,和AX
一起形成32的被除数。
50行,or不是真正的加法。这是因为每次是除以10,所以DL
中得到的余数,高四位必定是0x30低四位是0,高4位是3。所以or之后是相当于加
51行,push指令将DX
内容压入栈中。push指令在8086中只能压入一个字,但其后的32位64位处理器允许压入字,双字,四字。因此必须有关键字。
执行push指令,首先将SP的内容减去操作数的字长,然后把要压入栈的数据放到逻辑地址SS:SP
所在位置。
SP-2即0x0000-0x0002=0xFFFE,错位被忽略。Intel使用低端字节序,低字节在低地址,高字节在高地址。压栈操作是从高地址端向低地址短推进。
出栈并显示各个数位
57行,pop弹出到DX
中,然后将SP的内容加上操作数的字长
执行完最后一次出栈,SP的内容重新为0
进一步认识栈
引入栈和push、pop只是为了方便程序开发。临时保存一个数值到栈中使用push指令是最简洁最省事的。否则push ax 可为
sub sp,2
mov bx,sp
mov [ss:bx],ax
pop ax类似
mov bx,sp
mox ax,[ss:bx]
add sp,2
如果你想把临时数据都保存在数据段,那么你必须在数据段中开辟一些空间,并亲自维护一个指针来跟综这些数据的存入和取出。
-
保持栈的平衡,SP在做事之前的值和之后的值要相同,即push和pop数相等。
-
必须充分估计需要的栈空间,如果栈段和代码段属于同一内存段,栈定义得过小,程序编写不当,栈可能破坏有用数据。
-
请举例说明为什么在压栈操作中,栈指针需要先减去一个偏移量,然后再将数据存储到栈中在出栈操作中,在出栈操作中,栈指针需要先访问栈中的数据,然后再将栈指针向上移动一个偏移量。
假设我们有一个栈,初始时栈指针 SP 指向栈底的内存地址,栈中已经存储了两个元素。此时我们要进行一次压栈操作和一次出栈操作,来说明为什么在这两个操作中需要先进行偏移量处理。
假设要压入的数据为 0x1234,大小为 2 字节,压栈操作流程如下:
- 将栈指针 SP 减去 2,这是偏移量处理的过程,让 SP 指向栈顶空闲位置;
- 将数据 0x1234 存储到栈指针 SP 指向的内存位置中;
- 将栈指针 SP 指向 0x1234 存储的内存位置,此时 SP 指向栈顶元素的位置。
此时栈的情况如下:
栈顶 0x1234 元素2 0x5678 元素1 0x9abc 栈底 SP指向 接下来,我们进行一次出栈操作,将栈顶的元素弹出,出栈操作流程如下:
- 访问栈指针 SP 指向的内存位置,即访问栈顶元素 0x1234;
- 将栈指针 SP 加上 2,这是偏移量处理的过程,让 SP 指向下一个栈元素的位置;
- 返回栈顶元素 0x1234。
此时栈的情况如下:
栈顶 0x5678 元素2 0x5678 元素1 0x9abc 栈底 SP指向 可以看到,在进行压栈和出栈操作时,都需要先进行偏移量处理,这样才能保证栈指针的正确性和栈中数据的一致性。
7.6 程序的编译和运行
Bochs中察看栈的命令是"print-stack",默认显示当前栈中的16个字
7.7 8086处理器的寻址方式
寻址方式就是如何找到要操作的数据,以及如何找到存放操作结果的地方(基于16位处理器)
- 寄存器寻址
mov ax,cx
add bx,0xf000
inc dx
- 立即(数)寻址
add bx,0xf000
mov dx,label_a
标号在编译阶段转化为立即数
立即寻址的操作数位于指令中,是指令的一部分
- 内存寻址
段地址由4个段寄存器之一来提供,偏移地址要由指令来提供
内存寻址实际上就是要寻找偏移地址,这称为有效地址
即如何在指令中提供偏移地址,供处理器访问内存时使用
- 直接寻址
mov ax,[0x5c0f]
add word [0x0230],0x5000
xor byte [es:label_b],0x05
- 基址寻址
基址寻址就是在指令的地址部分使用基址寄存器BX
或BP
来提供偏移地址
mov [bx],dx //将DS的内容左移4位,加上基址寄存器BX的内容
add byte [bx-2],0x55 //可加减一个偏移量不改变bx内容
mov ax,[bp] //该寄存器的默认段寄存器是SS,常用于访问栈
高级语言里的函数调用,所有的参数都在栈中。为了能访问到那些被压在栈底的参数,就需要用到BP
。
- 变址寻址
寻址方式使用的是变址寄存器(索引寄存器)SI和DI - 基址变址寻址
将 string db 'abcdef到z’原地反向排列
mov bx,string
mov si,0
mov di,25
order:
mov ah,[bx+si]
mov al,[bx+di]
mov [bx+si],al
mov [bx+di],ah
inc si
dec di
cmp si,di
jl order
本章汇编:
;代码清单7-1
;文件名:c07_mbr.asm
;文件说明:硬盘主引导扇区代码
;创建日期:2011-4-13 18:02
00000000 E90E00 jmp near start
00000003 312B322B332B2E2E2E- message db '1+2+3+...+100='
0000000C 2B3130303D
start:
00000011 B8C007 mov ax,0x7c0 ;设置数据段的段基地址
00000014 8ED8 mov ds,ax
00000016 B800B8 mov ax,0xb800 ;设置附加段基址到显示缓冲区
00000019 8EC0 mov es,ax
;以下显示字符串
0000001B BE[0300] mov si,message
0000001E BF0000 mov di,0
00000021 B90E00 mov cx,start-message
@g:
00000024 8A04 mov al,[si]
00000026 268805 mov [es:di],al
00000029 47 inc di
0000002A 26C60507 mov byte [es:di],0x07
0000002E 47 inc di
0000002F 46 inc si
00000030 E2F2 loop @g
;以下计算1到100的和
00000032 31C0 xor ax,ax
00000034 B90100 mov cx,1
@f:
00000037 01C8 add ax,cx
00000039 41 inc cx
0000003A 83F964 cmp cx,100
0000003D 7EF8 jle @f
;以下计算累加和的每个数位
0000003F 31C9 xor cx,cx ;设置堆栈段的段基地址
00000041 8ED1 mov ss,cx
00000043 89CC mov sp,cx
00000045 BB0A00 mov bx,10
00000048 31C9 xor cx,cx
@d:
0000004A 41 inc cx
0000004B 31D2 xor dx,dx
0000004D F7F3 div bx
0000004F 80CA30 or dl,0x30
00000052 52 push dx
00000053 83F800 cmp ax,0
00000056 75F2 jne @d
;以下显示各个数位
@a:
00000058 5A pop dx
00000059 268815 mov [es:di],dl
0000005C 47 inc di
0000005D 26C60507 mov byte [es:di],0x07
00000061 47 inc di
00000062 E2F4 loop @a
00000064 E9FDFF jmp near $
00000067 00<rept> times 510-($-$$) db 0
000001FE 55AA db 0x55,0xaa