[彙編]《組合語言》第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:1000H和ds: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