王爽《匯編語言》第四版 超級筆記
第7章 更靈活的定位內存地址的方法
前面,我們用[0]、[bx]的方法,在訪問內存的指令中,定位內存單元的地址。
本章我們主要通過具體的問題來講解一些更靈活的定位內存地址的方法和相關的編程方法。
7.1 and 和 or 指令
首先,介紹兩條指令and和or,因為我們下面的例程中要用到它們。
(1)and指令:邏輯與指令,按位進行與運算。
例如指令:
mov al,01100011B
and al,00111011B
執行后:al=00100011B
通過該指令可將操作對象的相應位設為0,其他位不變。
例如:
將al的第6位設為0的指令是:and al,10111111B
將al的第7位設為0的指令是:and al,01111111B
將al的第0位設為0的指令是:and al,11111110B
(2)or指令:邏輯或指令,按位進行或運算。
例如指令:
mov al,01100011B
or al,00111011B
執行后:al=01111011B
通過該指令可將操作對象的相應位設為1,其他位不變。
例如:
將al的第6位設為1的指令是:or al,01000000B
將al的第7位設為1的指令是:or al,10000000B
將al的第0位設為1的指令是:or al,0000000IB
7.2 關於ASCII碼
計算機中,所有的信息都是二進制,而人能理解的信息是己經具有約定意義的字符。
比如說,人在有一定上下文的情況下看到“123”,就可知道這是一個數值,它的大小為123;
看到“BASIC”就知道這是在說BASIC這種編程語言;看到“desk”,就知道說的是桌子。
而我們要把這些信息存儲在計算機中,就要對其進行編碼,將其轉化為二進制信息進行存儲。而計算機要將這些存儲的信息再顯示給我們看,就要再對其進行解碼。
只要編碼和解碼采用同樣的規則,我們就可以將人能理解的信息存入到計算機,再從計算機中取出。
世界上有很多編碼方案,有一種方案叫做ASCII編碼,是在計算機系統中通常被釆用的。簡單地說,所謂編碼方案,就是一套規則,它約定了用什么樣的信息來表示現實對象。
比如說,在ASCII編碼方案中,用61H表示“a”,62H表示“b”。一種規則需要人們遵守才有意義。
一個文本編輯過程中,就包含着按照ASCII編碼規則進行的編碼和解碼。在文本編輯過程中,我們按一下鍵盤的a鍵,就會在屏幕上看到“a”。這是怎樣一個過程呢?
我們按下鍵盤的a鍵,這個按鍵的信息被送入計算機,計算機用ASCII碼的規則對其進行編碼,將其轉化為61H存儲在內存的指定空間中;文本編輯軟件從內存中取岀61H,將其送到顯卡上的顯存中;
工作在文本模式下的顯卡,用ASCH碼的規則解釋顯存中的內容,61H被當作字符“a”,顯卡驅動顯示器,將字符“a”的圖像畫在屏幕上。
我們可以看到,顯卡在處理文本信息的時候,是按照ASCII碼的規則進行的。這也就是說,如果我們要想在顯示器上看到“a”,就要給顯卡提供“a”的ASCII碼,61H。如何提供?當然是寫入顯存中。
7.3 以字符形式給出的數據、大小寫轉換的問題
我們可以在匯編程序中,用‘......’的方式指明數據是以字符的形式給出的,編譯器將把它們轉化為相對應的ASCII碼。如下面的程序。
程序7.1
assume cs:code,ds:data
data segment
db 'unlX'
db 'foRK'
data ends
code segment
start: mov al,'a'
mov bl,'b'
mov ax,4c00h
int 21h
code ends
end start
上面的源程序中:
“db 'unIX' ”相當於“db 75H,6EH,49H,58H”,“u”、“n”、“I”、“X”ASCII碼分別為75H、6EH、49H、58H;
“ db 'foRK' ”相當於“db 66H,6FH,52H,4BH”,“f”、“o”、“R”、“K"的ASCII碼分別為66H、6FH、52H、4BH;
“mov al, 'a'”相當於“mov al,61H”,“a”的ASCII碼為61H;
“mov bl, 'b'”相當於“mov al,62H”,“b”的ASCII碼為62H;
將程序7.1編譯為可執行文件后,用Debug加載查看data段中的內容。
圖7.1中,先用r命令分析一下data段的地址,因“ds=0B2D”,所以程序從0B3DH段開始,data段又是程序中的第一個段,它就在程序的起始處,所以它的段地址為0B3DH。
用d命令查看data段,Debug以十六進制數碼和ASCII碼字符的形式顯示岀其中的內容,可以看出data段中的每個數據所對應的ASCII字符。
下面考慮這樣一個問題,在codesg中填寫代碼,將datasg中的第一個字符串轉化為大寫,第二個字符串轉化為小寫。
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db'iNfOrMaTiOn'
datasg ends
codesg segment
start:
codesg ends
end start
首先分析一下,我們知道同一個字母的大寫字符和小寫字符對應的ASCII碼是不同的,比如“A”的ASCII碼是41H,“a”的ASCII碼是61H。
要改變一個字母的大小寫,實際上就是要改變它所對應的ASCII碼。我們可以將所有的字母的大寫字符和小寫字符所對應的ASCII碼列岀來,進行一下對比,從中找到規律。
通過對比,我們可以看出來,小寫字母的ASCII碼值比大寫字母的ASCII碼值大20H。
這樣,我們可以想到,如果將“a”的ASCII碼值減去20H,就可以得到“A”;如果將“A”的ASCII碼值加上20H就可以得到“a”。
要注意的是,對於字符串"BaSiC”,應只對其中的小寫字母所對應的ASCII碼進行減20H的處理,將其轉為大寫,而對其中的大寫字母不進行改變;對於字符串“iNfOrMaTiOn”,我們應只對其中的大寫字母所對應的ASCII碼進行加20H的處理,將其轉為小寫,而對於其中的小寫字母不進行改變。
這里面就存在着一個前提,程序必須要能夠判斷一個字母是大寫還是小寫。以“BaSiC”討論,程序的流程將是這樣的:
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends
codesg segment
start:mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s:mov al,[bx]
如果(al)>61H,則為小寫字母的ASCII碼,則:sub al,20H
mov [bx],al
inc bx
loop s
codesg ends
end start
我們前面所運用的規律是,小寫字母的ASCII碼值,比大寫字母的ASCII碼值大20H。
考慮問題的出發點是:大寫字母+20H=小寫字母,小寫字母-20H=大寫字母。這使我們最終落入了這樣一個矛盾之中:必須判斷是大寫字母還是小寫字母,才能決定進行何種處理,而我們現在又沒有可以使用的用於判斷的指令。
我們應該重新觀察,尋找新的規律。可以看出,就ASCII碼的二進制形式來看,除第5位(位數從0開始計算)外,大寫字母和小寫字母的其他各位都一樣。
大寫字母ASCII碼的第5位為0,小寫字母的第5位為1。這樣,我們就有了新的方法,一個字母,不管它原來是大寫還是小寫,將它的第5位置0,它就必將變為大寫字母;將它的第5位置1,它就必將變為小寫字母。
在這個方法中,我們不需要在處理前判斷字母的大小寫。比如:對於“BaSiC”中的“B”,按要求,它己經是大寫字母了,不應進行改變,將它的第5位設為0,它還是大寫字母,因為它的第5位本來就是0。
用什么方法將一個數據中的某一位置0還是置1?
當然是用我們剛剛學過的or和and指令。
完整的程序如下。
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends
codesg segment
start:mov ax,datasg
mov ds,ax ;設置ds指向datasg段
mov bx,0 ;設置(bx)=0,ds:bx指向'BaSiC'的第一個字母
mov cx,5 ;設置循環次數5,因為,BaSiC有5個字母
s: mov al,[bx] ;將ASCII碼從ds:bx所指向的單元中取出
and al,11011111B ;將al中的ASCII碼的第5位置為0,變為大寫字母
mov [bx],al ;將轉變后的ASCII碼寫回原單元
inc bx ;(bx)加1,ds:bx指向下一個字母
loop s
mov bx,5 ;設置(bx) =5,ds:bx指向'iNfOrMaTiOn'的第一個字母
mov cx,11 ;設置循環次數11,因為TNfOrMaTiOn,有11個字母
s0:mov al,[bx]
or al,00100000B ;將al中的ASCII碼的第5位置為1,變為小寫字母
mov [bx],al
inc bx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
7.4 [bx+idata]及[bx+idata]的方式進行數組處理
我們用[bx]的方式來指明一個內存單元,還可以用一種更為靈活的方式來指明內存單元:
[bx+idata]表示一個內存單元,它的偏移地址為(bx)+idata(bx中的數值加上idata)。
我們看一下指令mov ax,[bx+200]的含義:
將一個內存單元的內容送入ax,這個內存單元的長度為2個字節(字單元),存放一個字,偏移地址為bx中的數值加上200,段地址在ds中。
數學化的描述為:(ax)=((ds)x16+(bx)+200)
該指令也可以寫成如下格式(常用):
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200
問題7.1
用Debug查看內存,結果如下:
2000:1000 BE 00 06 00 00 00 ……
寫出下面的程序執行后,ax、bx、cx中的內容。
mov ax,2000H
mov ds,ax
mov bx,1000H
mov ax,[bx]
mov cx,[bx+1]
add cx,[bx+2]
思考后看分析。
分析:
mov ax,[bx]
訪問的字單元的段地址在ds中,(ds)=2000H;偏移地址在bx中,(bx)=1000H;指令執行后(ax)=00BEH。
mov cx,[bx+1]
訪問的字單元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+1=1001H;指令執行后
(cx)=0600H。
add cx,[bx+2]
訪問的字單元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+2=1002H;指令執行后
(cx)=0606H。
有了[bx+idata]這種表示內存單元的方式,我們就可以用更高級的結構來看待所要處理的數據。
我們通過下面的問題來理解這一點。
在codesg中填寫代碼,將datasg中定義的第一個字符串轉化為大寫,第二個字符串轉化為小寫。
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'MinlX1'
datasg ends
codesg segment
start:
codesg ends
end start
按照我們原來的方法,用[bx]的方式定位字符串中的字符。代碼段中的程序如下。
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx]
and al,11011111b
mov [bx],al
inc bx
loop s
mov bx,5
mov cx,5
s0:mov al,[bx]
or al,00100000b
mov [bx],al
inc bx
loop s0
現在,我們有了[bx+idata]的方式,就可以用更簡化的方法來完成上面的程序。
觀察datasg段中的兩個字符串,一個的起始地址為0,另一個的起始地址為5。我們可以將這兩個字符串看作兩個數組,一個從0地址開始存放,另一個從5開始存放。
那么我們可以用[0+bx]和[5+bx]的方式在同一個循環中定位這兩個字符串中的字符。在這里,0和5給定了兩個字符串的起始偏移地址,bx中給岀了從起始偏移地址開始的相對地址。
這兩個字符串在內存中的起始地址是不一樣的,但是,它們中的每一個字符,從起始地址開始的相對地址的變化是相同的。改進的程序如下。
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx] ;定位第一個字符串中的字符
and al,11011111b
mov [bx],al
mov al,[5+bx] ;定位第二個字符串中的字符
or al,00100000b
mov [5+bx],al
inc bx
loop s
程序也可以寫成下面的樣子:
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5
s: mov al,0[bx]
and al,11011111b
mov 0[bx],al
mov al,5[bx]
or al,00100000b
mov 5[bx],al
inc bx
loop s
如果用高級語言,比如c語言來描述上面的程序,大致是這樣的:
char a[5]="BaSiC";
char b[5]="MinIX";
main()
{
int i;
i=0;
do
{
a[i]=a[i] & 0xDF;
b[i]=b[i] | 0x20;
i++;
}
while(i<5);
}
如果你熟悉C語言的話,可以比較一下這個C程序和上面的匯編程序的相似之處。尤其注意它們定位字符串中字符的方式。
C語言:a[i],b[i]
匯編語言:0[bx],5[bx]
通過比較,我們可以發現,[bx+idata]的方式為高級語言實現數組提供了便利機制。
7.5 SI和DI、[bx+si]和[bx+di]、[bx+si+idata]和[bx+di+idata]
SI和DI是8086CPU中和bx功能相近的寄存器,si和di不能夠分成兩個8位寄存器來使用。
下面的3組指令實現了相同的功能。
(1) mov bx,0
mov ax,[bx](2) mov si,0
mov ax,[si](3)mov di,0
mov ax,[di]
下面的3組指令也實現了相同的功能。
(1) mov bx,0
mov ax,[bx+123](2) mov si,0
mov ax,[si+123](3) mov di,0
mov ax,[di+123]
問題7.2
用si和di實現將字符串'welcome to masm!'復制到它后面的數據區中。
assume cs:codesg,ds:datasg
datasg segment
db 'welcome to masm!'
db '................'
datasg ends
思考后看分析。
分析:
我們編寫的程序大都是進行數據的處理,而數據在內存中存放,所以我們在處理數據之前首先要搞清楚數據存儲在什么地方,也就是說數據的內存地址。
現在我們要對datasg段中的數據進行復制,先來看一下要復制的數據在什么地方,datasg:0,這是要進行復制的數據的地址。
那么復制到哪里去呢?
它后面的數據區。“welcome to masm! ”從偏移地址0開始存放,長度為16個字節,所以,它后面的數據區的偏移地址為16,就是字符串“................”存放的空間。
清楚了地址之后,我們就可以進行處理了。我們用ds:si指向要復制的源始字符串,用ds:di指向復制的目的空間,然后用一個循環來完成復制。代碼段如下。
codesg segment
start:mov ax,datasg
mov ds,ax
mov si,0
mov di,16
mov cx,8
s: mov ax,[si]
mov [di],ax
add si,2
add di,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
注意,在程序中,用16位寄存器進行內存單元之間的數據傳送,一次復制2個字節,一共循環8次。
問題7.3
用更少的代碼,實現問題7.2中的程序。
思考后看分析。
分析:
我們可以利用[bx(si或di)+idata]的方式,來使程序變得簡潔。程序如下。
codesg segment
start:mov ax,datasg
mov ds,ax
mov si,0
mov cx,8
s: mov ax,0[si]
mov 16[si],ax
add si,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
我們用[bx(si或di)]和[bx(si或di)+idata]的方式來指明一個內存單元,我們還可以用更為靈活的方式:[bx+si]和[bx+di]。
[bx+si]和[bx+di]的含義相似,我們以[bx+si]為例進行講解。
[bx+si]表示一個內存單元,它的偏移地址為(bx)+(si)(即bx中的數值加上si中的數值)。
指令mov ax,[bx+si]的含義如下:
將一個內存單元的內容送入ax,這個內存單元的長度為2字節(字單元),存放一個字,偏移地址為bx中的數值加上si中的數值,段地址在ds中。
數學化的描述為:(ax)=((ds)x16+(bx)+(si))
該指令也可以寫成如下格式(常用):
mov ax,[bx][si]
問題7.4
用Debug查看內存,結果如下:
2000:1000 BE 00 06 00 00 00 ……
寫出下面的程序執行后,ax、bx、cx中的內容。
mov ax,2000H
mov ds,ax
mov bx,1000H
mov si,0
mov ax,[bx+si]
inc si
mov cx,[bx+si]
inc si
mov di,si
add cx,[bx+di]
思考后看分析。
分析:
mov ax,[bx+si]
訪問的字單元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)=1000H;指令執行后(ax)=00BEH。
mov cx,[bx+si]
訪問的字單元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)=1001H;指令執行后(cx)=0600H。
add cx,[bx+di]
訪問的字單元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(di)=1002H;指令執行后(cx)=0606H。
[bx+si+idata]和[bx+di+idata]的含義相似,我們以[bx+si+idata]為例進行講解。
[bx+si+idata]表示一個內存單元,它的偏移地址為(bx)+(si)+idata(即bx中的數值加上si中的數值再加上idata)。
指令mov ax,[bx+si+idata]的含義如下:
將一個內存單元的內容送入ax,這個內存單元的長度為2字節(字單元),存放一個字,偏移地址為bx中的數值加上si中的數值再加上idata,段地址在ds中。
數學化的描述為:(ax)=((ds)x16+(bx)+(si)+idata)
該指令也可以寫成如下格式(常用):
mov ax,[bx+200+si]
mov ax,[200+bx+si]
mov ax,200[bx][si]
mov ax,[bx].200[si]
mov ax,[bx][si].200
問題7.5
用Debug查看內存,結果如下:
2000:1000 BE 00 06 00 6A 22.....
寫出下面的程序執行后,ax、bx、cx中的內容。
mov ax,2000H
mov ds,ax
mov bx,1000H
mov si,0
mov ax,[bx+2+si]
inc si
mov cx,[bx+2+si]
inc si
mov di,si
mov bx,[bx+2+di]
思考后看分析。
分析:
mov ax,[bx+2+si]
訪問的字單元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)+2=1002H;指令執行后(ax)=0006H。
mov cx,[bx+2+si]
訪問的字單元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)+2=1003H;指令執行后(cx)=6A00H。
mov bx,[bx+2+di]
訪問的字單元的段地址在ds中,(ds)=2000H;偏移地址=(bx)+(di)+2=1004H;指令執行后(bx)=226AH。
7.6 不同的尋址方式的靈活應用
如果我們比較一下前面用到的幾種定位內存地址的方法(可稱為尋址方式),就可以發現:
(1) [idata]用一個常量來表示地址,可用於直接定位一個內存單元;
(2) [bx]用一個變量來表示內存地址,可用於間接定位一個內存單元;
(3) [bx+idata]用一個變量和常量表示地址,可在一個起始地址的基礎上用變量間接定位一個內存單元;
(4) [bx+si]用兩個變量表示地址;
(5) [bx+si+idata]用兩個變量和一個常量表示地址。
可以看到,從[idata]一直到[bx+si+idata],我們可以用更加靈活的方式來定位一個內存單元的地址。
這使我們可以從更加結構化的角度來看待所要處理的數據。下面我們通過一個問題的系列來體會CPU提供多種尋址方式的用意,並學習一些相關的編程技巧。
問題7.6
編程,將datasg段中每個單詞的頭一個字母改為大寫字母。
分析:
datasg中的數據的存儲結構,如圖7.2所示。
我們可以看到,在datasg中定義了6個字符串,每個長度為16個字節(注意,為了直觀,每個字符串的后面都加上了空格符,以使它們的長度剛好為16個字節)。
因為它們是連續存放的,可以將這6個字符串看成一個6行16列的二維數組。按照要求,需要修改每一個單詞的第一個字母,即二維數組的每一行的第4列(相對於行首的偏移地址為3)。
我們需要進行6次循環,用一個變量R定位行,用常量3定位列。處理的過程如下。
R=第一行的地址
mov cx,6
s: 改變R行,3列的字母為大寫
R=下一行的地址
loop s
我們用bx作變量,定位每行的起始地址,用3定位要修改的列,用[bx+idata]的方式來對目標單元進行尋址,程序如下。
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,6
s : mov al,[bx+3]
and al,11011111b
mov [bx+3],al
add bx,16
loop s
問題7.7
編程,將datasg段中每個單詞改為大寫字母。
分析:
datasg中的數據的存儲結構如圖7.3所示。
在datasg中定義了4個字符串,每個長度為16個字節(注意,為了使我們在Debug中可以直觀地查看,每個字符串的后面都加上了空格符,以使它們的長度剛好為16個字節)。
因為它們是連續存放的,我們可以將這4個字符串看成一個4行16列的二維數組。按照要求,我們需要修改每一個單詞,即二維數組的每一行的前3列。
我們需要進行4x3次的二重循環,用變量R定位行,變量C定位列。外層循環按行來進行,內層按列來進行。
首先用R定位第1行,然后循環修改R行的前3列;然后再用R定位到下一行,再次循環修改R行的前3列……,如此重復直到所有的數據修改完畢。處理的過程大致如下。
R=第一行的地址;
mov cx,4
s0: C=第一列的地址
mov cx,3
s:改變R行,C列的字母為大寫
C=下一列的地址;
loop s
R=下一行的地址
loop s0
我們用bx來作變量,定位每行的起始地址,用si定位要修改的列,用[bx+si]的方式來對目標單元進行尋址,程序如下。
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0:mov si,0
mov cx,3
s : mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
loop s0
問題7.8
仔細閱讀上面的程序,看看有什么問題?
思考后看分析。
分析:
問題在於cx的使用,我們進行二重循環,卻只用了一個循環計數器,造成在進行內層循環的時候,覆蓋了外層循環的循環計數值。多用一個計數器又不可能,因為loop指令默認cx為循環計數器。怎么辦呢?
我們應該在每次開始內層循環的時候,將外層循環的cx中的數值保存起來,在執行外層循環的loop指令前,再恢復外層循環的cx數值。
可以用寄存器dx來臨時保存cx中的數值,改進的程序如下。
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0:mov dx,cx ;將外層循環的cx值保存在dx中
mov si,0
mov cx,3 ;cx設置為內層循環的次數
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,dx ;用dx中存放的外層循環的計數值恢復cx
loop s0 ;外層循環的loop指令將cx中的計數值減1
上面的程序用dx來暫時存放cx中的值,如果在內層循環中,dx寄存器也被使用,該怎么辦?
我們似乎可以使用別的寄存器,但是CPU中的寄存器數量畢竟是有限的,如8086CPU只有14個寄存器。
可是如果循環中的程序比較復雜,這些寄存器也都被使用的話,那么該如何?
我們在這里討論的問題是,程序中經常需要進行數據的暫存,怎樣做將更為合理。這些數據可能是寄存器中的,也可能是內存中的。
我們可以用寄存器暫存它們,但是這不是一個一般化的解決方案,因為寄存器的數量有限,每個程序中可使用的寄存器都不一樣。我們希望尋找一個通用的方案,來解決這種在編程中經常會出現的問題。
顯然,我們不能選擇寄存器,那么可以使用的就是內存了。可以考慮將需要暫存的數據放到內存單元中,需要使用的時候,再從內存單元中恢復。這樣我們就需要開辟一段內存空間。再次改進的程序如下。
assume cs:codesg,ds:datasg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
dw 0 ;定義一個字,用來暫存cx
datasg ends
codesg segment
start:mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0:mov ds:[40H],cx ;將外層循環的cx值保存在datasg:40H單元中
mov si,0
mov cx,3 ;cx設置為內層循環的次數
s : mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
mov cx,ds:[40H] ;用datasg:40H單元中的值恢復cx
loop s0 ;外層循環的loop指令將cx中的計數值減1
mov ax,4c00H
int 21H
codesg ends
end start
上面的程序中,用內存單元來保存數據,可是上面的做法卻有些麻煩,因為如果需要保存多個數據的時候,你必須要記住數據放到了哪個單元中,這樣程序容易混亂。
我們使用內存來暫存數據,這一點是確定了的,但是值得推敲的是,我們用怎樣的結構來保存這些數據,而使得我們的程序更加清晰。
一般來說,在需要暫存數據的時候,我們都應該使用棧。
回憶一下,棧空間在內存中,采用相關的指令,如push、pop等,可對其進行特殊的操作。下面,再次改進我們的程序。
assume cs:codesg,ds:datasg,ss:stacksg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends
stacksg segment ;定義一個段,用來做棧段,容量為16個字節
dw 0,0,0,0,0,0,0,0
stacksg ends
codesg segment
start:mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0:push cx ;將外層循環的cx值壓棧
mov si,0
mov cx,3 ;cx設置為內層循環的次數
s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s
add bx,16
pop cx ;從棧頂彈出原cx的值,恢復cx
loop s0 ;外層循環的loop指令將cx中的計數值減1
mov ax,4c00H
int 21H
codesg ends
end start
問題7.9
編程將datasg段中的每個單詞的前4個字母改為大寫字母。
assume cs:codesg,ss:stacksg,ds:datasg
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
datasg segment
db '1.display '
db '2.brows '
db '3.replace '
db '4.modify '
datasg ends
codesg segment
start:
codesg ends
end start
分析:
datasg中的數據的存儲結構,如圖7.4所示。
在datasg中定義了4個字符串,每個長度為16字節(注意,為了使我們在Debug中可以直觀地查看,每個字符串的后面都加上了空格符,以使它們的長度剛好為16個字節)。
因為它們是連續存放的,我們可以將這4個字符串看成一個4行16列的二維數組,按照要求,我們需要修改每個單詞的前4個字母,即二維數組的每一行的3~6列。
我們需要進行4x4次的二重循環,用變量R定位行,常量3定位每行要修改的起始列,變量C定位相對於起始列的要修改的列。外層循環按行來進行,內層按列來進行。
我們首先用R定位第1行,循環修改R行的3+C(0<=C<=3)列;然后再用R定位到下一行,再次循環修改R行的3+C(0<=C<=3)列……,如此重復直到所有的數據修改完畢。處理的過程大致如下。
R=第一行的地址;
mov cx,4
s0: C=第一個要修改的列相對於起始列的地址
mov cx,4
s: 改變R行,3+C列的字母為大寫
C=下一個要修改的列相對於起始列的地址
loop s
R=下一行的地址
loop s0
我們用bx來作變量,定位每行的起始地址,用si定位要修改的列,用[bx+3+si]的方式來對目標單元進行尋址。
這一章中,我們主要講解了更靈活的尋址方式的應用和一些編程方法,主要內容有:
-
尋址方式[bx(或si、di)+idata]、[bx+si(或di)]、[bx+si(或di)+idata]的意義和應用;
-
二重循環問題的處理;
-
棧的應用;
-
大小寫轉化的方法;
-
and、or 指令。
下一章中,我們將對尋址方式的問題進行更深入的探討。之所以如此重視這個問題,是因為尋址方式的適當應用,使我們可以以更合理的結構來看待所要處理的數據。而為所要處理的看似雜亂的數據設計一種清晰的數據結構是程序設計的一個關鍵的問題。