[匯編]《匯編語言》第8章 數據處理的兩個基本問題


王爽《匯編語言》第四版 超級筆記

第8章 數據處理的兩個基本問題

本章對前面的所有內容是具有總結性的。

計算機是進行數據處理、運算的機器,那么有兩個基本的問題就包含在其中:

  • 處理的數據在什么地方?

  • 要處理的數據有多長?

這兩個問題,在機器指令中必須給以明確或隱含的說明,否則計算機就無法工作。

本章中,我們就要針對8086CPU對這兩個基本問題進行討論。雖然討論是在8086CPU的基礎上進行的,但是這兩個基本問題卻是普遍的,對任何一個處理器都存在。

我們定義的描述性符號:reg和sreg。

為了描述上的簡潔,在以后的課程中,我們將使用描述性的符號reg來表示一個寄存器,用sreg表示一個段寄存器。

reg的集合包括:ax、bx、ex、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;

sreg的集合包括:ds、ss、cs、es。

8.1 bx、si、di和bp

寄存器總結如下:

(1) 在8086CPU中,只有這4個寄存器可以用在“[...]”中來進行內存單元的尋址。比如下面的指令都是正確的:

mov ax,[bx]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp]
mov ax,[bp+si]
mov ax,[bp+di]

而下面的指令是錯誤的:

mov ax,[cx]
mov ax,[ax]
mov ax,[dx]
mov ax,[ds]

(2) 在[...]中,這4個寄存器可以單個出現,或只能以4種組合出現:bx和si、bx和di、bp和si、bp和di。比如下面的指令是正確的:

mov ax,[bx]
mov ax,[si]
mov ax,[di]
mov ax,[bp]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp+si]
mov ax,[bp+di]
mov ax,[bx+si+idata]
mov ax,[bx+di+idata]
mov ax,[bp+si+idata]
mov ax,[bp+di+idata]

下面的指令是錯誤的:

mov ax,[bx+bp]
mov ax,[si+di]

(3) 只要在[…]中使用寄存器bp,而指令中沒有顯性地給岀段地址,段地址就默認在ss中。比如下面的指令。

mov ax,[bp] 含義:(ax)=((ss)x16+(bp))

mov ax,[bp+idata] 含義:(ax)=((ss)x16+(bp)+idata)

mov ax,[bp+si] 含義:(ax)=((ss)x16+(bp)+(si))

mov ax,[bp+si+idata] 含義:(ax) =((ss)x16+(bp)+(si)+idata)

8.2 機器指令處理的數據在什么地方、數據位置的表達

絕大部分機器指令都是進行數據處理的指令,處理大致可分為3類:讀取、寫入、運算。

在機器指令這一層來講,並不關心數據的值是多少,而關心指令執行前一刻,它將要處理的數據所在的位置。

指令在執行前,所要處理的數據可以在3個地方:CPU內部、內存、端口(端口將在后面的課程中進行討論),比如表8.1中所列的指令。

image


在匯編語言中如何表達數據的位置?匯編語言中用3個概念來表達數據的位置。

(1)立即數(idata)

對於直接包含在機器指令中的數據(執行前在CPU的指令緩沖器中),在匯編語言中稱為:立即數(idata),在匯編指令中直接給出。

例:
mov ax,1
add bx,2000h
or bx,00010000b
mov al,'a'

(2)寄存器

指令要處理的數據在寄存器中,在匯編指令中給岀相應的寄存器名。

例:
mov ax,bx
mov ds,ax
push bx
mov ds:[0],bx
push ds
mov ss,ax
mov sp,ax

(3)段地址(SA)和偏移地址(EA)

指令要處理的數據在內存中,在匯編指令中可用[X]的格式給出EA,SA在某個段寄存器中。

存放段地址的寄存器可以是默認的,比如:

mov ax,[0]
mov ax,[di]
mov ax,[bx+8]
mov ax,[bx+si]
mov ax,[bx+si+8]

等指令,段地址默認在ds中;

mov ax,[bp]
mov ax,[bp+8]
mov ax,[bp+si]
mov ax,[bp+si+8]

等指令,段地址默認在ss中。

存放段地址的寄存器也可以是顯性給出的,比如以下的指令。

mov ax,ds:[bp]            含義:(ax)=((ds)x16+(bp))
mov ax,es:[bx]             含義:(ax)=((es)x16+ (bx))
mov ax,ss:[bx+si]        含義:(ax)=((ss)x16+(bx)+(si))
mov ax,cs:[bx+si+8]    含義:(ax)=((cs)x16+(bx)+(si)+8)

8.3 尋址方式、尋址方式的綜合應用

當數據存放在內存中的時候,我們可以用多種方式來給定這個內存單元的偏移地址,這種定位內存單元的方法一般被稱為尋址方式。

8086CPU有多種尋址方式,我們在前面的課程中都己經用到了,這里進行一下總結,如表8.2所列。

image


下面我們通過一個問題來進一步討論一下各種尋址方式的作用。

關於DEC公司的一條記錄(1982年)如下。

公司名稱:DEC
總裁姓名:Ken Olsen
排 名:137
收 入:40(40億美元)
著名產品:PDP(小型機)

這些數據在內存中以圖8.1所示的方式存放。

image

可以看到,這些數據被存放在seg段中從偏移地址60H起始的位置,從seg:60起始以ASCII字符的形式存儲了3個字節的公司名稱;從seg:60+3起始以ASCII字符的形式存儲了9個字節的總裁姓名;從seg:60+0C起始存放了一個字型數據,總裁在富翁榜上的排名;從seg:60+0E起始存放了一個字型數據,公司的收入;從seg:60+10起始以ASCII字符的形式存儲了3個字節的產品名稱。

以上是該公司1982年的情況,到了1988年DEC公司的信息有了如下變化。

(1)Ken Olsen在富翁榜上的排名己升至38位;
(2)DEC的收入增加了70億美元;
(3)該公司的著名產品己變為VAX系列計算機。

我們提出的任務是,編程修改內存中的過時數據。

首先,我們應該分析一下要修改的數據。

要修改內容是:

(1)(DEC公司記錄)的(排名字段)
(2)(DEC公司記錄)的(收入字段)
(3)(DEC公司記錄)的(產品字段)的(第一個字符)、(第二個字符)、(第三個字符)

從要修改的內容,我們就可以逐步地確定修改的方法。

(1)要訪問的數據是DEC公司的記錄,所以,首先要確定DEC公司記錄的位置:

R=seg:60

確定了公司記錄的位置后,下面就進一步確定要訪問的內容在記錄中的位置。

(2)確定排名字段在記錄中的位置:0CH。
(3)修改R+0CH處的數據。
(4)確定收入字段在記錄中的位置:0EH。
(5)修改R+0EH處的數據。
(6)確定產品字段在記錄中的位置:10H。
要修改的產品字段是一個字符串(或一個數組),需要訪問字符串中的每一個字符。所以要進一步確定每一個字符在字符串中的位置。

(7)確定第一個字符在產品字段中的位置:P=0。
(8)修改R+10H+P處的數據:P=P+1。
(9)修改R+10H+P處的數據:P=P+1。
(10)修改R+10H+P處的數據。

根據上面的分析,程序如下。

mov ax,seg
mov ds,ax
mov bx,60h      ;確定記錄地址,ds:bx

mov word ptr [bx+0ch],38        ;排名字段改為38
add word ptr [bx+Oeh],70        ;收入字段增加70

mov si,0        ;用si來定位產品字符串中的字符
mov byte ptr [bx+10h+si],'V'
inc si
mov byte ptr [bx+10h+si],'A'
inc si
mov byte ptr [bx+10h+si],'X'

如果你熟悉c語言的話,我們可以用c語言來描述這個程序,大致應該是這樣的:

struct company (     /*定義一個公司記錄的結構體*/
        char cn[3];        /*公司名稱*/
        char hn[9];        /*總裁姓名*/
        int pm;            /*排   名*/
        int sr;            /*收   入*/
        char cp[3];        /*著名產品*/
};

struct company dec={"DEC","Ken Olsen",137,40,"PDP"};        /*定義一個公司記錄的變量,內存中將存有一條公司的記錄*/

main()
{
    int i;
    dec.pm=38;
    dec.sr=dec.sr+70;
    i=0;
    dec.cp[i]='V';
    i++;
    dec.cp[i]='A';
    i++;
    dec.cp[i]='X';
    return 0;
}

我們再按照c語言的風格,用匯編語言寫一下這個程序,注意和C語言相關語句的比對:

mov ax,seg
mov ds,ax
mov bx,60h      ;記錄首址送BX

mov word ptr [bx].0ch,38        ;排名字段改為38   ;C:dec.pm=38;

add word ptr [bx].0eh,70        ;收入字段增加70   ;C: dec.sr=dec.sr+70;   ;產品字段改為字符串'VAX'

mov si,0        ;C:i=0;
mov byte ptr [bx].10h[si],'V'       ;dec.cp[i]='V';
inc si      ;i++;
mov byte ptr [bx].10h[si],'A'       ;dec.cp[i]='A';
inc si      ;i++;
mov byte ptr [bx].10h[si],'X'       ;dec.cp[i]='X';

我們可以看到,8086CPU提供的如[bx+si+idata]的尋址方式為結構化數據的處理提供了方便。使得我們可以在編程的時候,從結構化的角度去看待所要處理的數據。

從上面可以看到,一個結構化的數據包含了多個數據項,而數據項的類型又不相同,有的是字型數據,有的是字節型數據,有的是數組(字符串)。

一般來說,我們可以用[bx+idata+si]的方式來訪問結構體中的數據。用bx定位整個結構體,用idata定位結構體中的某一個數據項,用si定位數組項中的每個元素。為此,匯編語言提供了更為貼切的書寫方式,如:[bx].idata、[bx].idata[si]。

在C語言程序中我們看到,如:dec.cp[i],dec是一個變量名,指明了結構體變量的地址,cp是一個名稱,指明了數據項cp的地址,而i用來定位cp中的每一個字符。匯編語言中的做法是:bx.10h[si]。

8.4 指令要處理的數據有多長

8086CPU的指令,可以處理兩種尺寸的數據:byte和word。

所以在機器指令中要指明,指令進行的是字操作還是字節操作。對於這個問題,匯編語言中用以下方法處理。

(1)通過寄存器名指明要處理的數據的尺寸。

例如,下面的指令中,寄存器指明了指令進行的是字操作。

mov ax,1
mov bx,ds:[0]
mov ds,ax
mov ds:[0],ax
inc ax
add ax,1000

下面的指令中,寄存器指明了指令進行的是字節操作。

mov al,1
mov al,bl
mov al,ds:[0]
mov ds:[0],al
inc al
add al,100

(2)在沒有寄存器名存在的情況下,用操作符X ptr 指明內存單元的長度,X在匯編指令中可以為word或byte。

例如,下面的指令中,用word ptr指明了指令訪問的內存單元是一個字單元。

mov word ptr ds:[0],1
inc word ptr [bx]
inc word ptr ds:[0]
add word ptr [bx],2

下面的指令中,用byte ptr指明了指令訪問的內存單元是一個字節單元。

mov byte ptr ds:[0],1
inc byte ptr [bx]
inc byte ptr ds:[0]
add byte ptr [bx],2

在沒有寄存器參與的內存單元訪問指令中,用word ptr或byte ptr顯性地指明所要訪問的內存單元的長度是很必要的。否則,CPU無法得知所要訪問的單元是字單元,還是字節單元。

假設我們用Debug查看內存的結果如下:

2000:1000 FF FF FF FF FF FF ......

那么指令:

mov ax,2000H
mov ds,ax
mov byte ptr [1000H],1

將使內存中的內容變為:

2000:1000 01 FF FF FF FF FF .....

而指令:

mov ax,2000H
mov ds,ax
mov word ptr [1000H],1

將使內存中的內容變為:

2000:1000 01 00 FF FF FF FF .....

這是因為mov byte ptr [1000H],1訪問的是地址為ds:1000H的字節單元,修改的是
ds:1000H單元的內容;而mov word ptr [1000H],1訪問的是地址為ds:1000H的字單元,修改的是ds:1000Hds:1001H兩個單元的內容。

(3)其他方法

有些指令默認了訪問的是字單元還是字節單元,比如,push [1000H] 就不用指明訪問的是字單元還是字節單元,因為push指令只進行字操作。

8.5 div 指令、偽指令 dd、dup

div是除法指令,使用div做除法的時候應注意以下問題。

(1) 除數:有8位和16位兩種,在一個reg或內存單元中。

(2) 被除數:默認放在AX或DX和AX中,如果除數為8位,被除數則為16位,默認在AX中存放;如果除數為16位,被除數則為32位,在DX和AX中存放,DX存放高16位,AX存放低16位。

(3) 結果:如果除數為8位,則AL存儲除法操作的商,AH存儲除法操作的余數;如果除數為16位,則AX存儲除法操作的商,DX存儲除法操作的余數。

格式如下:

div reg
div 內存單元

我們可以用多種方法來表示一個內存單元了,比如下面的例子:

div byte ptr ds:[0]
含義:(al)=(ax)/((ds)x16+0)的商
     (ah)=(ax)/((ds)x16+0)的余數

div word ptr es:[0]
含義:(ax)=[(dx)x10000H+(ax)]/((es)x16+0)的商
     (dx)=[(dx)x10000H+(ax)]/((es)x16+0)的余數

div byte ptr [bx+si+8]
含義:(al)=(ax)/((ds)x16+(bx)+(si)+8)的商
     (ah)=(ax)/((ds)x16+(bx)+(si)+8)的余數

div word ptr [bx+si+8]
含義:(ax)=[(dx)x10000H+(ax)]/((ds)x16+ (bx)+(si)+8)的商
     (dx)=[(dx)x10000H+(ax)]/((ds)x16+(bx)+(si)+8)的余數

編程,利用除法指令計算100001/100。

首先分析一下,被除數100001大於65535,不能用ax寄存器存放,所以只能用dx和ax兩個寄存器聯合存放100001,也就是說要進行16位的除法。

除數100小於255,可以在一個8位寄存器中存放,但是,因為被除數是32位的,除數應為16位,所以要用一個16位寄存器來存放除數100。

因為要分別為dx和ax賦100001的高16位值和低16位值,所以應先將100001表示為16進制形式:186A1H。程序如下:

mov dx,1
mov ax,86A1H ;(dx)x10000H+(ax)=100001
mov bx,100
div bx

程序執行后,(ax)=03E8H(即1000),(dx)=1(余數為1)。讀者可自行在Debug中實踐。

編程,利用除法指令計算1001/100。

首先分析一下,被除數1001可用ax寄存器存放,除數100可用8位寄存器存放,也就是說,要進行8位的除法。程序如下。

mov ax,1001
mov bl,100
div bl

程序執行后,(al)=0AH(即10),(ah)=1(余數為1)。讀者可自行在Debug中實踐。


前面我們用db和dw定義字節型數據和字型數據。dd是用來定義dword(double word,雙字)型數據的。比如:

data segment
    db 1
    dw 1
    dd 1
data ends

在data段中定義了3個數據:

  • 第一個數據為01H,在data:0處,占1個字節;
  • 第二個數據為0001H,在data:1處,占1個字;
  • 第三個數據為00000001H,在data:3處,占2個字。

問題8.1

用div計算data段中第一個數據除以第二個數據后的結果,商存在第三個數據的存儲單元中。

data segment 
    dd 100001
    dw 100
    dw 0
data ends

思考后看分析。

分析:

data段中的第一個數據是被除數,為dword(雙字)型,32位,所以在做除法之前,用dx和ax存儲。

應將data:0字單元中的低16位存儲在ax中,data:2字單元中的高16位存儲在dx中。程序如下。

mov ax,data
mov ds,ax
mov ax,ds:[0]       ;ds:0字單元中的低16位存儲在ax中
mov dx,ds:[2]       ;ds:2字單元中的高16位存儲在dx中
div word ptr ds:[4] ;用dx:ax中的32位數據除以ds:4字單元中的數據
mov ds:[6],ax       ;將商存儲在ds:6字單元中

dup是一個操作符,在匯編語言中同db、dw、dd等一樣,也是由編譯器識別處理的符號。

它是和db、dw、dd等數據定義偽指令配合使用的,用來進行數據的重復。比如:

db 3 dup (0)

定義了3個字節,它們的值都是0,相當於db 0,0,0。

db 3 dup (0,1,2)

定義了9個字節,它們是0、1、2、0、1、2、0、1、2,相當於db 0,1,2,0,1,2,0,1,2。

db 3 dup ('abc','ABC')

定義了18個字節,它們是'abcABCabcABCabcABC',相當於
db 'abcABCabcABCabcABC'。

可見,dup的使用格式如下。

db 重復的次數 dup (重復的字節型數據)
dw 重復的次數 dup (重復的字型數據)
dd 重復的次數 dup (重復的雙字型數據)

dup是一個十分有用的操作符,比如要定義一個容量為200個字節的棧段,如果不用dup,則必須:

stack segment
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
    dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends

當然,你可以用dd,使程序變得簡短一些,但是如果要求定義一個容量為1000字節或10000字節的呢?

如果沒有dup,定義部分的程序就變得太長了,有了dup就可以輕松解決。如下:

stack segment
    db 200 dup (0)
stack ends


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM