第8章 比高斯更快的计算

第8章 比高斯更快的计算

该章通过从1加到100的功能,介绍了数据结构——栈、处理器的寻址方式。

书中例程的功能就是在页面输出:1+2+3+…+100=5050。

从1加到100的故事

计算机不懂的等差数列求和公式,但是它运行速度很快,从1一直加到100,就是一眨眼的事。

代码清单8-1

作者提供了源码,可以查看配书源码和工具的c08文件夹。

显示字符串

该章节主要介绍了程序时如何实现显示 ‘1+2+3+…+100’ 这个字符串的。

和上一章的方式基本类似,就是在声明字符串的时候,采用一起声明的的方式了。

;第七章的方式
; 相当于把字符和显示属性放在一起声明
mytext db 'L',0x07,'a',0x07,'b',0x07,'e',0x07,'l',0x07,' ',0x07,'o',0x07,\
    'f',0x07,'f',0x07,'s',0x07,'e',0x07,'t',0x07,':',0x07
    
;第八章的方式
; 只声明字符串相关内容,显示属性在循环显示的时候赋值为0x07。
message db '1+2+3+...+100=' ;声明了字符串。

这样就更加直观了。可以看出来,书中的例子都是循序渐进改进的。

计算1到100的累加和

思路上很简单,代码也不难理解。

思路

  • ax清0,存储每次累加的和。
  • cx从1~100依次累加到ax中。

代码:书中代码没有注释,我给代码加了一些注释。

;以下计算1到100的和 
xor ax,ax          ; ax清0
mov cx,1           ; cx初值为1
@f:
    add ax,cx          ; (ax)=(ax)+(cx)
    inc cx             ; (cx)++
    cmp cx,100         ; 比较cx和100
    jle @f             ; jle(Jump Less equal) cx<=100则继续循环

溢出问题

ax最大只能表示65535,这里和为5050,不会溢出。

累加和各个数位的分解与显示

栈和栈段的初始化

上一章是将分解出来的位数保存在事先声明的数据段,该章使用进行处理。

(Stack)是一种特殊的数据存储结构,数据的存取只能从一端进行,遵循先进后出(FILO:First Input Last Output)的原则。

栈通过栈段SS(Stack Segment)和栈指针SP(Stack Pointer)进行访问。

栈操作:

  • push 推入数据
  • pop 弹出数据

image

书中栈段的初始化地址是:0x0000:0x0000~0x0000:0xFFFF

根据代码段、数据段、扩展段等其他段的设置可以得到如下内存映像:

image

分解各个数位并压栈

书中代码我加一些注释:

    mov bx,10         ;余数放在bx中
    xor cx,cx         ;(cx)=0,用cx记录循环的次数,即数的为位数
@d:
    inc cx            ;每次循环 cx+1
    xor dx,dx         ;相当于 mov dx,0
    div bx          
    or dl,0x30        ;相当于加上0x30
    push dx           ;将余数推入栈,一次推入两个字节
    cmp ax,0          ;检测商是否为0
    jne @d            ;商不为0,则继续循环

程序第50行为什么用 or dl,0x30

因为除以10的余数为0~9,1只可能出现低4位
0000 1111(低4位)
or 0011 0000(0x30)
所以使用or和add是一样的效果。

当该代码执行完成时,栈中的情况如下所示:

image

第一次压入栈时,SP=0x0000 - 2,高位会被舍弃掉,所以就是0xFFFE,这个地方不太好理解。

为什么不能压入一个字节?

因为个、十、百、千每个数字都是1个字节就够了,为什么要压入两个字节?

因为push最少只能接受16位的操作数。如果是压入内存,需要指定关键词 word、dword。

出栈并显示各个数位

书中代码多写了一些注释:

     ;以下显示各个数位 
 @a:
     pop dx            ;将栈中的数据弹一个字(2字节),存储到dx
     mov [es:di],dl    ;显示的字符
     inc di            ;后面一个字节表示显示的属性
     mov byte [es:di],0x07 ;字符显示的属性0000_0111,黑底白字
     inc di            ;继续下一个字符
     loop @a

循环次数cx在前面分解数字的时候就算好了,所以这里没有体现。

使用栈先进后出的方式在显示数值的时候非常方便。

进一步认识栈

第一,push指令的操作数可以是16位寄存器或者16位内存单元,push指令执行后,压入栈中的仅仅是该寄存器或者内存单元里的数值,与该寄存器或内存单元不再相干。

第二,栈在本质上也只是普通的内存区域,之所以要用push和pop指令来访问,是因为你把它看成栈而已。

第三,要注意保持栈平衡。push和pop的次数要一样多。

第四,在编写程序前,必须充分估计所需要的栈空间,以防止破坏有用的数据。

第五,尽管不能完全阻止程序中的错误,但是,通过将栈定义到一个单独的64KB段,可以使错误仅局限于栈,而不破坏其他段的有用数据。

程序的编译和运行

观察程序的运行结果

image

在调试过程中查看栈中内容

使用 print-stack n 可以查看栈中的内容:

image

8086处理器的寻址方式

既然操作和处理的是数值,那么,必定涉及数值从哪里来,处理后送到哪里去,这称为寻址方式(Addressing Mode)。

寄存器寻址

最简单的寻址方式是寄存器寻址。

;直接操作寄存器
mov ax,cx     
add bx,0xf000
inc dx

立即寻址

立即数寻址,指令的操作数是一个立即数:

add bx,0xf000  ;bx加上1个立即数 0xf000
mov dx,label_a ;标号会被转化为数值

一般高级语言叫做常量。

内存寻址

寄存器寻址的操作数位于寄存器中,立即寻址的操作数位于指令中,是指令的一部分。

所谓的内存寻址方式,就是如何在指令中指定操作数的偏移地址,供处理器访问内存时使用,这个偏移地址也叫有效地址(Effective Address, EA)。句话说,内存寻址方式就是在指令中指定偏移地址(有效地址)如何计算。

1.直接寻址

mov ax,[0x5c0f]             ; DS*16+0x5c0f
add word [0x0230],0x5000    ; DS*16+0x0230
xor byte [es:label_b],0x50  ; es*16+label_b的地址  

2.基址寻址

基址寻址:在指令的地址部分使用基址寄存器BX或者BP来提供一个基准地址。

引例,将buffer中的每个字+1。

buffer dw 0x20,0x100,0x0f,0x300,0xff00
inc word [buffer]
inc word [buffer+2]
inc word [buffer+4]

逐行处理,这种方式不太方便。如果有100个,就要写100行。

基址寻址:基地址+位移

mov [bx],dx        ; DS*16+bx <- (dx)
add byte [bx],0x55 ; DS*16+bx <- 0x55

随意访问栈中的内容

mov ax,0x5000      ;将 0x5000 压入栈中
push ax 
mov bp,sp          ;将指向0x5000的指针保存下来           
mov ax,0x7000      ;再压入一个 0x7000
push ax            
mov dx,[bp]        ; dx <- (ss*16+bp), 即0x5000

可以为基址寄存器BP加上一个位移,即可访问栈其他位置的内容:

mov dx,[bp-2]      ; dx <- (ss*16+bp-2), 即0x7000

改进引例:

mov bx,buffer    ;buffer标号所在地址为基地址
mov cx,5         ;循环5次
lpinc:
    inc word [bx] ;+1
    add bx,2      ;指向下一个地址
    loop lpinc            

3.变址寻址

变址寻址类似于基址寻址,唯一不同之处在于这种寻址方式使用的是变址寄存器(或称索引寄存器)SI和DI。

mov [si],dx            ; ds*16+si <- (dx)
add ax,[di]            ; ax <- ds*16+di
xor word [si],0x8000   ; ds*16+si <- 0x8000  

带偏移量

mov [si+0x100],al            ; ds*16+si+0x100 <- al
and byte [di+label_a],0x80   ; ds*16+di+label_a <- 0x80

4.基址变址寻址

将前面的基址和变址结合起来。

引例,将字符串反向排列。

string db 'abcdefghijklmnopqrstuvwxyz'
;先把字母逐个弹入栈。
mov cx,26
mov bx,string
lppush:
    mov al,[bx]
    push ax
    inc bx
    loop lppush

;依次弹出。
mov cx,26
mov bx,string
lppop:
    pop ax
    mov [bx],al
    inc bx
    loop lppop

使用基址寄存器(BX或者BP),变址寄存器(SI或者DI)

mov ax, [bx+si]            ; ax <- (ds*16+bx+si)
mov word [bx+di], 0x3000   ; ds*16+bx+di <- 0x3000

改进引例:

mov bx, string
mov si, 0        ;源从0开始,一个从前
mov di, 25       ;目的从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

使用基址寄存器和变址寄存器允许一个数值位移:

mov [bx+si+0x100],al
and byte [bx+di+label_a],0x80

完。

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值