王爽《匯編語言》第四版 超級筆記
第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
