1. 程式人生 > 其它 >[彙編]《組合語言》第8章 資料處理的兩個基本問題

[彙編]《組合語言》第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中所列的指令。


在組合語言中如何表達資料的位置?組合語言中用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所列。


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

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

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

這些資料在記憶體中以圖8.1所示的方式存放。

可以看到,這些資料被存放在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