將數據代碼放入不同的段
彙編語言程序可以將數據,棧和代碼都放到一個段裏面,但是也可以將程序,棧和代碼分別放到不同的段裏,下圖就是定義多個段的程序
assume cs:codesg,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
codesg segment
start:mov ax,stack
mov ss,ax
mov sp,20h ;設置棧頂指向stack:20
mov ax,data
mov ds,ax ;設置ds指向data段
mov bx,0
mov cx,8
s:push [bx]
add bx,2
loop s
mov bx,0
mov cx,8
s0:pop [bx]
add bx,2
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
上述程序定義了三個段,分別是:
- 存放數據的數據段
- 存放堆棧數據的堆棧段
- 存放程序的代碼段
上圖所示的代碼中有一個需要注意的地方是堆棧指針的取值,也就是
mov sp,20h
要將明白sp的取值,就不得不明白數據在內存中的存儲形式,下圖示字在內存中的存儲,內存中存儲的數據分別是:4E20H和0012H。這裏要明白字和字節的換算關係:
1字節 = 8位 (1byte = 8bite)
1字 = 2字節 = 16位 (1word = 2byte = 16bite)
由上圖可以知道,內存中存儲4E20H這個數據時,是把4E20這個數據拆分成了兩個數據進行分別存儲,4E和20,並將高位存儲在高地址中,把低位存儲在低地址中。
回過頭來,再來分析SP的值,我們可以知道在初始化時Stack中存放了如下所示的數據:
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
上述數據是字型的,也就是一個數據有2個字節,因此,棧中所有的數據所佔的存儲空間是16*2 = 32個字節,而32是10進制數,轉換爲16進制也就是20H,,因此堆棧指針的初始值應該設置爲20H。
and 和 or指令
爲了更好的解釋and和or指令,這裏通過大小寫字符轉換的問題爲例子來說明這兩個指令的作用。
大小寫字母的關係
要實現大小寫字符的轉換,那就需要知道大小寫字符之間的關係是什麼。我們分別列出大小寫字符的ASCII碼,如下表所示:
大寫 | 十六進制 | 二進制 | 小寫 | 十六進制 | 二進制 |
---|---|---|---|---|---|
A | 41 | 01000001 | a | 61 | 01100001 |
B | 42 | 01000010 | b | 62 | 01100010 |
C | 43 | 01000011 | c | 63 | 01100011 |
通過對比,我們可以發現。小寫字母的ASCII碼比都比大寫字母的ASCII碼要大20H,這反映在二進制上,也就是大寫字母的第六位總是0,小寫字母的第六位總是1,因此,要實現大小寫字母的轉換,只需要將小寫字母的第六位變爲0即可。
下面代碼關於and和or的例子:
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]
or al,00100000b
mov [bx],al
inc bx
loop s
mov bx,5
mov cx,11
s1:mov al,[bx]
and al,11011111b
mov [bx],al
inc bx
loop s1
mov ax,4c00h
int 21h
codesg ends
end start
用[bx+idata]的方式進行數組的處理
上述程序我們用[bx]的方式來指明一個內存單元,還可以用一種更加靈活的方式來指明內存單元:[bx+idata]表示一個內存單元,它的偏移地址爲(bx)+idata。下面舉一個例子說明這個語句的用法:
mov ax,[bx+200]
上述語句的含義是:將一個內存單元的內容送入ax,這個內存單元的長度爲2個字節(字單元),存放一個字。偏移地址爲bx中的數值加上200,段地址在ds中。
數學描述如下:
(ax) = ((ds)*16+(bx)+200)
下圖所示代碼是用[bx+idata]的方式進行數組的處理
assume cs:codeseg,ds:dataseg
dataseg segment
db 'BaSiC' ;大寫變小寫 0->1
db 'MinIX' ;小寫變大寫 1->0
dataseg ends
codeseg segment
start:mov ax,dataseg
mov ds,ax
mov bx,0
mov cx,5
s:mov al,[bx]
;mov al,0[bx]
or al,00100000b
mov [bx],al
;mov 0[bx],al
mov al,[bx+5]
;mov al,5[bx]
and al,11011111b
mov [bx+5],al
;mov 5[bx],al
inc bx
loop s
mov ax,4c00h
int 21h
codeseg ends
end start
添加了[bx+idata]的方式,實現了類似於C原因數組的效果,其中註釋掉的代碼是作爲[bx+idata]的另一種表達形式。也是可以進行運行的。
SI 和 DI
si和di是8086CPU中和bx功能相近的寄存器,需要注意的是si和di不能夠分成兩個8位寄存器來使用。下面的三組指令實現的同樣的功能
(1)mov bx,0
mov ax,[bx]
(2)mov si,0
mov ax,[si]
(3)mov di,0
mov ax,[di]
下面的代碼利用si和di將字符串複製到它後面的區域中去:
assume cs:codeseg,ds:datasg
datasg segment
db 'welcome to masm!'
db '................'
datasg ends
codeseg segment
start:mov ax,datasg
mov ds,ax
mov si,0
mov cx,8
s:mov ax,[si]
;mov ax,0[si]
mov [si+16],ax
;mov 16[si],ax
add si,2
loop s
mov ax,4c00h
int 21h
codeseg ends
end start
上述代碼中運用來了si實現了同bx一樣的功能,但是與bx不同的一點是,si只實現以字爲單位的運算,因此,雖要複製得字符串有16個字節,但是loop循環的次數卻只有8次。
[bx+si]和[bx+di]
在上述的代碼中,我們使用了形如[bx(si或di)]和[bx(si或di)+idata]的方式來進行尋址,這裏介紹一種更爲靈活的尋址方式:[bx+si]、[bx+di]
現有如下指令:
mov ax,[bx+si]
上述指令的含義是:將一個內存單元送入ax,這個內存單元的長度是2字節(字單元),存放一個字,偏移地址爲bx中數值加上si中的數值,段地址在ds中。數學化表示爲:
(ax) = ((ds)*16+(bx)+(si))
該指令也可以寫成如下常用的形式:
mov ax,[bx][si]
下面爲[bx+si指令的簡單運用:
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]
上述代碼的主要作用就是分別訪問了2000:1000、2000:1001、2000:1002的地址的數據,分別將其賦值給ax,cx。
[bx+si+idata]和[bx+di+idata]
[bx+si+idata]表示一個內存單元,它的偏移地址爲(bx)+si+idata。
現有人如下指令
mov ax,[bx+si+idata]
用數學表達式描述爲:
(ax)=((ds)*16+(bx)+(si)+idata)
該指令通常也可以寫成如下的形式:
mov ax,[bx][si].200
下面試運用[bx+si+idata]處理問題的相關程序
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:mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s0:push cx
mov si,0
mov cx,4
s:mov ax,[bx][si].3
;mov ax,[bx+3+si]
and al,11011111b
;mov [bx+3+si],ax
mov [bx][si].3,ax
inc si
loop s
add bx,16
pop cx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
上述代碼的作用是將dataseg段的每個單詞前的四個字母改爲大寫字母,程序的實現思路是採用瞭如下的幾種方法
- 循環嵌套的方法。
- 第一層循環用於控制對哪一個單詞進行尋址,用於每一個單詞的字節大小均爲16,因此在操作完第一個單詞後,只需要將bx加16就可定位到下一個單詞。
- 第二層循環用於控制對單詞的各個字母進行尋址,從而能夠改變到每一個單詞的各個字母。
- 堆棧保存循環次數
- cx值壓棧。由於控制彙編語言進行循環次數的寄存器均採用的是cx寄存器,因此,在第二層循環執行前必須將cx的值保存下來,這裏採用堆棧的機制保存cx的值,在第二層循環執行前將cx的值進行壓棧。
- cx值出棧。 然後爲了能夠使第一層循環正常運行,在第二層循環執行完一次後,需要將堆棧裏的值彈出,賦值給cx,從而保證了第一層循環的正確運行。
- cx值壓棧。由於控制彙編語言進行循環次數的寄存器均採用的是cx寄存器,因此,在第二層循環執行前必須將cx的值保存下來,這裏採用堆棧的機制保存cx的值,在第二層循環執行前將cx的值進行壓棧。
指令要處理的數據長度
8086CPU中可以處理兩種長度的數據,因此,在機器指令中要指明處理的數據長度是多長的。在彙編語言中,主要存在如下幾種方法來指明數據的長度:
- 通過寄存器名指明要處理的數據的尺寸
- 字操作:mov ax,1 、mov ds,ax
- 字節操作:mov al,1、mov al,bl
- 在沒有寄存器名存在的情況下,用操作符X pyr指明內存單元的長度
- 字操作:mov word ptr ds:[0],1、inc word ptr [bx]
- 字節操作:mov byte ptrr ds:[0],1、inc byte ptr ds:[0]
div指令
div是除法指令,使用div的時候應該注意如下的問題:
- 除數:有8位和16位兩種,在一個reg或內存單元中。
- 被除數:默認放在AX或DX和AX中,如果除數爲8位,被除數則爲16位,默認在AX中存放;如果除數爲16位,被除數則爲32位,在DX和AX中存放,DX中存放高16位,AX存放低16位
- 結果:如果除數爲8位,則AL存儲除法操作的商,AH存儲除法操作的餘數;如果除數爲16位,則AX存儲除法操作的商,DX存儲除法操作的餘數。
具體操作例子:
div byte ptr ds:[0]
含義:
(al) = (ax)/((ds)*16+0)的商
(ah)=(ax)/((ds)*16+0)的餘數
div word ptr es:[0]
含義:
(ax)= [(dx)*10000H+(ax)]/((es)*16+0)的商
(ax)= [(dx)*10000H+(ax)]/((es)*16+0)的餘數
僞指令dd,db,dw,dup
- db:字節型數據
- dw:字型數據
- dd:定義雙字型數據
比如如下例子:
data segment
dd 100001
dw 100
dw 0
data ends
- dup:dup是一個操作符,在彙編語言中同db、dw、dd等一樣,也是由編譯器識別處理的符號。
比如如下例子:
db 3 dup (0)
相當於 db 0,0,0
db 3 dup (0,1,2)
相當於 db 0,1,2,0,1,2,0,1,2
使用dd能夠使得程序變得更加簡潔,簡短。
綜合例子
尋址方式在結構化數據訪問中的應用
詳細的程序代碼:
assume cs:codeseg,ds:dataseg,es:tableseg
dataseg segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;以上是表示21年的21個字符
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上是表示21年公司總收入的21個dword型數據
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;以上表示21年公司僱員人數的21個word型數據
dataseg ends
tableseg segment
db 21 dup ('year summ ne ?? ')
tableseg ends
codeseg segment
start:mov ax,dataseg
mov ds,ax
mov ax,tableseg
mov es,ax
mov bx,0
mov si,0
mov di,0
mov cx,21
s:mov ax,[bx]
mov es:[si],ax ;先將年份的低位存入
mov ax,[bx+2]
mov es:[si+2],ax ;後將年份的高位存入
mov ax,[bx+84]
mov es:[si+5],ax ;先將總收入的低位存入
mov ax,[bx+84+2]
mov es:[si+7],ax ;後將總收入的高位存入
mov ax,[di+168]
mov es:[si+10],ax ;將僱員人數的低位存入
mov ax,[bx+84] ;將總收入的低位存入ax
mov dx,[bx+84+2] ;將總收入的高位存入dx
div word ptr ds:[di+168]
mov es:[si+13],ax
add si,16
add bx,4
add di,2
loop s
mov ax,4c00h
int 21h
codeseg ends
end start
下圖是代碼運行結束後內存中各個數據的存儲位置
由圖中可以看出:
- (1)號標號所框選的內容就是具體的年份存儲的形式,因爲年份在存儲的時候採用的是字符存儲的方式,每一個數字佔一個字節,一共佔了4個字節。另外,字符在計算機內存中存儲時採用的是以ASCII碼的形式進行存儲,圖中的每一個數字代碼一個字節。
- (2)號標號中所框選的內容就是年份存儲年份後的一個空格,空格在內存中的存儲值是20H。因此,對於table列表中的其他空格存儲的值都是20H。圖中的第四列,第六列,第8列也都是存儲的空格。
- (3)號標號中存儲的是總的收入,因爲總的收入需要佔用4個字節,因此,也就需要四列來存儲所有的收入指。其中,在進行讀取的時候,要遵循高位數據存放在高位內存中,低位數據存放在低位內存中。例如,在讀取最後一個數據時,正確的順序應該是00 5A 97 68
- (5)號標號存儲的內容是公司的僱員數,僱員數佔2個字節,也就是圖中佔兩列
- (7)號標號存儲的內容是公司員工的人均收入,佔兩個字節。