?? 匯006.txt
字號:
了解的內容:段各類屬性(如:對齊類型、組合類型等)的含義,源程序的各種輔助說明偽指令。
掌握的內容:段寄存器說明語句的作用,堆棧段定義的特殊性。
熟練掌握的內容:段的完整定義和簡化定義。
程序的三大結構(順序結構、分支結構和循環結構等)在匯編語言中的表現形式,高級語言的程序結構向匯編語言的程序結構轉換的一般方法。
匯編語言的常用編程工具——MASM或Tubro Debug——的使用,能在該編程環境下完成程序的編輯、匯編、調試和運行等步驟。
建議學習時間:10小時。第6章 程序的基本結構
在前面幾章,我們分別介紹了用匯編語言進行程序設計所需要的幾個最基本的知識:內存單元的尋址方式,變量定義和各種匯編指令格式。在掌握了這些基本內容之后,就需要學習如何把它們組成一個完整的匯編語言程序。
6.1 源程序的基本組成
匯編語言源程序的組成部分有:模塊、段、子程序和宏等。一個模塊對應一個目標文件,當開發較大型的應用程序時,該程序可能由若干個目標文件或庫結合而成的。有關模塊和子程序的知識和宏在第7章介紹,有關宏的知識將在第9章中敘述。
6.1.1 段的定義
微機系統的內存是分段管理的,為了與之相對應,匯編語言源程序也分若干個段來構成。8086CPU有四個段寄存器,在該系統環境下運行的程序在某個時刻最多可訪問四個段,而80386及其以后的CPU都含有六個段寄存器,于是,在這些系統環境下開發的運行程序在某個時刻最多可訪問六個段。
不論程序在某個時刻最多能訪問多少個段,在編程序時,程序員都可以定義比該段數更多的段。在通常情況下,一個段的長度不能超過64K,在80386及其以后系統的保護方式下,段基地址是32位,段的最大長度可達4G。
段的長度是指該段所占的字節數:
、如果段是數據段,則其長度是其所有變量所占字節數的總和;
、如果段是代碼段,則其長度是其所有指令所占字節數的總和。
在定義段時,每個段都有一個段名。在取段名時,要取一個具有一定含義的段名。
段定義的一般格式如下:
段名 SEGMENT [對齊類型] [組合類型] [類別]
… ;段內的具體內容
…
段名 ENDS
其中:“段名”必須是一個合法的標識符,前后二個段名要相同。可選項“對齊類型”、“組合類型”和“類別”的說明作用請見6.3節中的敘述。
一個數據段的定義例子:
DATA1 SEGMENT
word1 DW 1, 9078H, ?
byte1 DB 21, 'World'
DD 12345678H
DATA1 ENDS
一個代碼段的例子:
CODE1 SEGMENT
MOV AX, DATA1 ;把數據段DATA1的段值送AX
MOV DS, AX ;把AX的值送給DS,即:DS存儲數據段的段值
…
MOV AX, 4C00H
INT 21H ;調用DOS功能,結束程序的運行
CODE1 ENDS
6.1.2 段寄存器的說明語句
在匯編語言源程序中可以定義多個段,每個段都要與一個段寄存器建立一種對應關系。建立這種對應關系的說明語句格式如下:
ASSUME 段寄存器名:段名[, 段寄存器名:段名, ……]
其中:段寄存器是CS、DS、ES、SS、FS和GS,段名是在段定義語句說明時的段名。
在一條ASSUME語句中可建立多組段寄存器與段之間的關系,每種對應關系要用逗號分隔。例如,
ASSUME CS:CODE1, DS:DATA1
上面的語句說明了:CS對應于代碼段CODE1,DS對應于數據段DATA1。
在ASSUME語句中,還可以用關鍵字NOTHING來說明某個段寄存器不與任何段相對應。下面語句說明了段寄存器ES不與某段相對應。
ASSUME ES:NOTHING
在通常情況下,代碼段的第一條語句就是用ASSUME語句來說明段寄存器與段之間的對應關系。在代碼段的其它位置,還可以用另一個ASSUME語句來改變前面ASSUME語句所說明的對應關系,這樣,代碼段中的指令就用最近的ASSUME語句所建立的對應關系來確定指令中的有關信息。
例6.1 匯編語言段及其段說明語句的作用。
DATA1 SEGMENT ;定義數據段DATA1
word1 DW 5678h
byte1 DB "ABCDEFG"
DATA1 ENDS
DATA2 SEGMENT ;定義數據段DATA2
word2 DW 1234h
word3 DW 9876h
DATA2 ENDS
DATA3 SEGMENT ;定義數據段DATA3
byte2 DB ?
DATA3 ENDS
CODE1 SEGMENT ;編寫代碼段CODE1
ASSUME CS:CODE1, DS:DATA1, ES:DATA2 ;(1)
MOV AX, DATA1 ;(2)
MOV DS, AX ;(3)
MOV AX, DATA2 ;(4)
MOV ES, AX ;(5)
…
MOV AX, word1 ;訪問段DATA1中的字變量word1
MOV word2, AX ;訪問段DATA2中的字變量word2
…
ASSUME DS:DATA3, ES:NOTHING ;(6)
MOV AX, DATA3
MOV DS, AX
MOV BL, byte2 ;訪問段DATA3中的字節變量byte2
…
MOV AX, 4C00H ;(7)
INT 21H ;(8)
CODE1 ENDS
語句(1)和(6)分別說明了段和段寄存器之間的對應關系,其中語句(6)重新說明語句(1)所指定的對應關系。
二組語句(2)和(3)、(4)和(5)實現對段寄存器DS和ES賦初值。ASSUME說明語句只起說明作用,它不會對段寄存器賦值,所以,必須對有關段寄存器賦值。在以后的其它源程序中也都是用此方法來實現對數據段寄存器賦值的。
語句(7)和(8)是調用中斷21H的4CH號功能來結束本程序的執行,程序的返回代碼由寄存器AL來確定。結束本程序執行的指令是所有主模塊必須書寫的語句。
注意:代碼段寄存器不能由程序員在源程序中對其賦值,其值是由操作系統在裝入它進入系統運行時自動賦值的。
6.1.3 堆棧段的說明
堆棧段是一個特殊的段,在程序中可以定義它,也可以不定義。除了要生成COM型執行文件的源程序外,一個完整的源程序一般最好定義堆棧段。如果在程序中不定義堆棧段,那么,操作系統在裝入該執行程序時將自動為其指定一個64K字節的堆棧段。
在程序沒有定義堆棧段的情況下,在由連接程序生成執行文件時,將會產生一條如下的警告信息,但程序員可以不理會它,所生成的執行文件是可以正常運行的。
warning xxxx: no stack segment (其中:xxxx是錯誤號)
在源程序中,可用以下方法來定義堆棧段。
方法1:
STACK1 SEGMENT
DB 256 DUP(?) ;256是堆棧的長度,可根據需要進行改變
TOP LABEL WORD
STACK1 ENDS
以上堆棧段的定義如圖6.1所示。由于堆棧是按地址從大到小的存儲單元順序來存放內容的,所以,在堆棧存儲單元說明語句之后,再說明一個棧頂別名,這樣,對棧頂寄存器SP的賦值就很方便。
在源程序的代碼段中,還要添加如下程序段,才能把段STACK1當作堆棧段來使用。
圖6.1 堆棧段定義示意圖
…
ASSUME SS:STACK1 ;可在代碼段的段指定語句中一起說明
CLI ;禁止響應可屏蔽中斷
MOV AX, STACK1
MOV SS, AX
MOV SP, offset TOP ;給堆棧段的棧頂寄存器SP賦初值
STI ;恢復響應可屏蔽中斷
…
方法2:
STACK1 SEGMENT STACK ;定義一個堆棧段,其段名為STACK1
DB 256 DUP(?)
STACK1 ENDS
上述段定義說明了該段是堆棧段,系統會自動把段寄存器SS和棧頂寄存器SP與該堆棧段之間建立相應的關系,并設置其初值,而不用在代碼段對它們進行賦值。
除了以上二種方法外,還有一種更簡潔的方法來定義堆棧段,有關內容請見第6.4.2節中的敘述。
6.1.4 源程序的結構
下面的程序是一個完整的源程序,其功能是在屏幕上顯示字符串“Hello, World.”。讀者可參考此結構編寫自己的簡單程序。
例6.2 在屏幕上顯示字符串“HELLO,WORLD.”
解:可運行下面的控件,用鼠標左鍵單擊程序中的某一行,可閱讀其含義;單擊“內存”可切換內存內容的顯示方式。
偽指令END表示源程序到此為止,匯編程序對該語句之后的任何內容都不作處理,所以,通常情況下,偽指令END是源程序的最后一條語句。
偽指令END后面可附帶一個在程序中已定義的標號,由該標號指明程序的啟動位置。
如果源程序是一個獨立的程序或主模塊,那么,偽指令END后面一定要附帶一個標號;如果源程序僅是一個普通模塊,那么,其END后面就一定不能附帶標號。
6.2 程序的基本結構
在學習高級語言程序設計時,我們知道了程序的三大主要結構:順序結構、分支結構和循環結構。在匯編語言的源程序也同樣有此三大結構,所不同的是它們的表現形式不同。用高級語言編寫程序時,由于不使用“轉移語句”而使這三種結構清晰明了。
但在匯編語言的源程序中,很難不使用“轉移語句”(除非是一些只有簡單功能的程序),有時甚至會有各種各樣的“轉移語句”。由于存在這些轉移語句,就使得:匯編語言源程序的基本結構顯得不太明確。如果源程序的編寫者思維混亂,編寫出來的源程序在結構上就會顯得雜亂無章,反之,如果編寫者條理清晰,安排的操作井然有序,那么,編寫出來的程序在結構上就會一目了然。
總之,不論是高級語言的源程序,還是匯編語言的源程序,其程序的三大基本結構也還是萬變不離其宗的。
6.2.1 順序結構
順序結構是最簡單的程序結構,程序的執行順序就是指令的編寫順序,所以,安排指令的先后次序就顯得至關重要。另外,在編程序時,還要妥善保存已得到的處理結果,為后面的進一步處理直接提供前面的處理結果,從而避免不必要的重復操作。
例6.3 編寫程序段,完成下面公式的計算(其中:變量X和Y是32位有符號數,變量A,B和Z是16位有符號數)。
A←(X-Y+24)/Z的商,B←(X-Y+24)/Z的余數
解:
DATA1 SEGMENT
X DD ?
Y DD ?
Z DW ?
A DW ?
B DW ?
…
DATA1 ENDS
CODE1 SEGMENT
…
MOV AX, X
MOV DX, X+2 ;用(DX:AX)來保存32位變量X的數值
SUB AX,Y
SBB DX, Y+2 ;(DX:AX)-(Y+2:Y)
ADD AX, 24D
ADC DX, 0 ;(DX:AX)+24
IDIV Z
MOV A, AX
MOV B, DX
…
CODE1 ENDS
在編程序時,常常需要交換二變量之值。假設需要交換值的變量名為:var1和var2,臨時增加的變量名為temp。常用的算法如下:
temp = var1
var1 = var2
var2 = temp
例6.4 假設有二個字變量word1和word2,編寫程序段實現交換其值的功能。
解:
方法1:用匯編語言指令簡單“直譯”上面的 交換數據方法
DATA1 SEGMENT
…
word1 DW ?
word2 DW ?
temp DW ?
…
DATA1 ENDS
CODE1 SEGMENT
…
MOV AX, word1
MOV temp, AX ;上二語句實現語句“temp=word1”
MOV AX, word2
MOV word1, AX ;上二語句實現語句“word1=word2”
MOV AX, temp
MOV word2, AX ;上二語句實現語句“word2=temp”
…
CODE1 ENDS
這種方法雖然也能完成功能,但顯然其不能充分利用匯編語言的特點,程序效率很低。
方法2:用匯編語言指令的特點來直接編譯
DATA1 SEGMENT
…
word1 DW ?
word2 DW ?
…
DATA1 ENDS
CODE1 SEGMENT
…
MOV AX, word1
XCHG AX, word2
MOV word1, AX ;能XCHG word1, word2來代替這三條指令嗎?
…
CODE1 ENDS
該方法充分利用了匯編語言的特點,不僅省去了中間變量temp的定義,而且程序的效率也提高了。
6.2.2 分支結構
分支結構是一種非常重要的程序結構,也是實現程序功能選擇所必要的程序結構。由于匯編語言需要書寫轉移指令來實現分支結構,而轉移指令肯定會破壞程序的結構,所以,編寫清晰的分支結構是掌握該結構的重點,也是用匯編語言編程的基本功。
在程序中,當需要進行邏輯分支時,可用每次分二支的方法來達到程序最終分多支的要求,也可是用地址表的方法來達到分多支的目的。
一、顯示轉移指令實現的分支結構
在高級語句中,分支結構一般用IF語句來實現,在匯編語言中,課用無條件轉移指令或條件轉移指令實現的分支結構。如圖6.2給出了二種常用的分支結構。
在編寫分支程序時,要盡可能避免編寫“頭重腳輕”的結構,即:當前分支條件成立時,將執行一系列指令,而條件不成立時,所執行的指令很少。這樣就使后一個分支離分支點較遠,有時甚至會遺忘編寫后一分支程序。這種分支方式不僅不利于程序的閱讀,而且也不便將來的維護。
所以,在編寫分支結構時,一般先處理簡單的分支,再處理較復雜的分支。對多分支的情況,也可遵循“由易到難”的原則。因為簡單的分支只需要較少的指令就能處理完,一旦處理完這種情況后,在后面的編程過程中就可集中考慮如何處理復雜的分支。
(a) if … endif結構 (b) if…else…endif結構
圖6.2 分支結構的二種結構
例6.5 已知字節變量CHAR1,編寫一程序段,把它由小寫字母變成大寫字母。
解:
DATA1 SEGMENT
…
CHAR1 DB ?
…
DATA1 ENDS
CODE1 SEGMENT
…
MOV AL, CHAR1
CMP AL, ‘a’
JB next
CMP AL, ‘z’
JA next
SUB CHAR1, 20H ;指令AND CHAR1, 0DFH也可以
next: …
…
CODE1 ENDS
例6.6 編寫一程序段,計算下列函數值。其中:變量X和Y是有符號字變量。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -