MASM宏使用總結
導語
MASM(Macro Assembler)是由微軟公司提供的彙編工具,雖然有些年頭了,但是仍然存在於 vc.net這樣比較新的工具中。有很多彙編教科書以這個為對象,講述了如何用彙編去設計一個程式,作為電腦科學系學生的基礎課。但是,講述的內容大體上還是停留在5.1版,而且停留在DOS的時代。雖然提到了win32下的彙編,但是並沒有放在首要的位置。另一個被忽視的是作為MASM最大特色的宏,怎麼去看待彙編工具中提供的強大的宏,以及怎麼和在什麼場合下使用宏,語焉不詳。本文是作者在大量使用MASM宏,搭建了一個彙編環境下的OOP系統後,作的一個總結。
善用宏,能夠減少重複編碼,以及構建強大的功能,是重用代碼,美化代碼的一個有力的工具。宏在進階語言中是一個應該被極力避免的東西,在低級語言中確未必如此。
宏就是預先處理
宏就是在代碼被彙編成為obj檔案之前進行的預先處理。由於發生在彙編期(Assembly-Time, 和進階語言中的編譯期是一個意思),所以不會給執行期帶來負擔,可以用作代碼產生工具,設定和C++中的模板一樣,用作meta-programming的工具。在MASM中宏可以分為兩種:1、 Text Macro 2、Procedure (Function) Macro。第一種宏就是和#define pi 31415926這樣的簡單的文本替換的宏,第二種就是那種帶參數,可以有局部變數,可以傳回值這樣的可以看作函數或者過程的宏。下面就先從Text Macro入手,看看如何使用簡單的宏。
簡單的文本宏
你可以給一個字元序列指定一個符號名,然後在原始碼的其餘部分用這個名字來代替這個字元序列。這個指定了名字的文本就是文本宏。說白了就是文本替換。用TEXTEQU來定義一個這樣的宏。
name TEXTEQU <text>
name TEXTEQU textvar
name TEXTEQU %numvar
我這裡給出的使用說明和MASM Programmer Guide中給出的不大一樣,但是這個更能說明問題。我在這兒只解釋第一個用法,後面的用法將在講了“彙編期變數”之後再講。舉一些使用的例子。
pi TEXTEQU <3.1416>
DWPTR TEXTEQU <WORD PTR>
arg1 TEXTEQU <[bp+4]>
然後在代碼中就可以用pi這些名字來代替3.1416這些。<>表示他們是字串,如果把不加<> 則會把你給出的字串當作一個彙編期的文本變數來進行求值,而這樣的話會出錯的。
彙編期的變數與常量
這個幾個東西其實都有自己的名字,其實按照用法來說就是彙編期的常量與變數的意思。比如Text Macro(對,就是前面的文本宏)用作彙編期的文本常量,Name Assignment用作彙編期的數值變數。
定義彙編期常量
什麼是彙編期常量呢?其實也就是常量的意思,因為無論在編譯期還是執行期它都是靜態,一旦定義之後其值不能改變。回憶在C中,你用#define來定義常量。但是#define可以改變一個宏所等於的值,也就是說常量與否需要你的維護(編譯器會給出一個警告)。在MASM 中有一個關鍵字專門用來定義常量,嘗試改變常量的值會得到一個錯誤提示。
name EQU expression
name EQU <text>
第一個是用作定義個“數值”常量,第二個是用作定義“文本”常量。以後要特別區分開文本和數值。
定義彙編期文本變數
彙編期文本變數是對“text macro”的另外一個看法。其實它們是同一個事情。當你定義了一個文本宏之後,你可以把那個宏名看作彙編期文本變數的名字,被宏名替換的常值內容作為變數的文本值。
那麼前面說過的第二種用法:name TEXTEQU textvar就很好理解了。就是讓把一個文本變數賦給另一個文本變數。比如:
talent TEXTEQU <genius>
taowen TEXTEQU talent
第一行定義了一個名字為talent的彙編期文本變數,第二行把talent的值賦給了名為 taowen的變數。從結果上看這個和:
talent TEXTEQU <genius>
taowen TEXTEQU <talent>
是一樣的,但是第二種做法是先因為文本宏替換的作用把talent變成了genius。實際的效果是這樣的:
taowen TEXTEQU <genius>
顯示文本變數的內容
在C中經常用printf,在運行期顯示一些變數的內容來進行調試。而在MASM中則用echo來在彙編期顯示文本變數的內容。
china TEXTEQU <great country>
%echo china
這樣會在彙編時的命令列中出現great country。如果你把%號去掉,則顯示的是china。你應該可以推測出%是幹什麼的了,就是對一個變數進行求值。
定義彙編期數值變數
常量有兩種那麼變數也應該有兩種。這裡就介紹彙編期數值變數的用法。
name = expression
expression是一個數值運算式,比如:
val = 3+4
此時val就是一個數值變數,其值為7。你也可以這麼寫:
valexp TEXTEQU <3+4>
val = valexp
看上去好像式把一個文本變數賦給了數值變數,進行了類型轉換(呵呵,效果是一樣的)。其實實際上是把3+4寫到了valexp處,因為文本宏進行了文本替換。
把數值變數賦給文本變數
前面我們看到了如何“把文本變數賦給數值變數”,那麼反過來呢?
val = 3+4
valexp TEXTEQU val
結果是提示錯誤:STest.asm(15) : error A2051: text item required。彙編器說需要文本項,那麼我們加上<>就好了。
val = 3+4
valexp TEXTEQU <val>
用%echo valexp檢查一下你就會發現,並不是如你所願的顯示的是7,而是val。這個是因為<>使得彙編器認為val是一個字串,由於數值變數不是文本替換的宏,並不會把val替換為7,所以當然顯示的是val。正確的做法是:
val = 3+4
valexp TEXTEQU %val
%號和前面的用法一樣,是用作求值。回憶一開始介紹的文本宏的用法中的第三條就是: name TEXTEQU %numvar。這個用法就是讓一個數值變數的值賦給文本變數,經常用作顯示一個數值變數的值。調試的時候這麼寫。
pi = 3.1415926
temp TEXTEQU %pi
%echo temp
這個是一個很重要的調試技巧。
宏過程和宏函數
前面從簡單的文本宏引出彙編期的常量與變數。如果僅僅是用在宏外的代碼中,一個文本宏作一些簡單的替換就足夠了。它們更多的是用在複雜的宏中,這些宏可以看作過程和函數。同彙編期的變數一樣,它們是用在彙編期的。
下面將不再把text macro視為宏,而把它視為文本變數。宏直接指宏過程或者宏函數。宏過程是不帶傳回值的宏,而宏函數是帶傳回值的宏。它們都可以帶參數,也都可以有局部變數。其實可以統一的成為宏函數,或者彙編期函數
用如下的格式建立一個簡單的宏
name MACRO
statements
ENDM
statements中可以進行判斷或者迴圈,可以說是非常的全功能。但是彙編期的函數和執行期的函數是很不一樣的,一個是發生在彙編期的預先處理,另一個是把執行期的執行位置改變,執行一段代碼之後返回。
clear_eax_m MACRO
xor eax, eax
ENDM
clear_eax_p Proc
xor eax, eax
ret
clear_eax_p Endp
關於這個兩者的區別,我假定你已經理解了,如果不理解可以參考任何一本彙編教科書,上面有完整的彙編代碼說明為什麼不同。
給宏傳遞參數
參數對於函數的重要性不言而喻,對於宏的參數如下定義。
name MACRO parameterlist
statements
ENDM
簡單情況下,對於parameterlist就是參數名字用,號格開,比如:
clear_reg MACRO reg
xor reg, reg
ENDM
調用的時候,用這種格式:
clear_reg eax
對於宏過程,這個是唯一的調用格式。
參數的傳遞和執行期的函數的參數傳遞也是很不一樣的。參數是被直接替換的。你可以作這麼一個實驗:
TestMacro MACRO param
echo param
%echo param
ENDM
TextVar TEXTEQU <Hello>
TestMacro TextVar
輸出的結果是TextVar和Hello。到底是怎麼回事就不用我多說了。你甚至可以進一步測試:
TestMacro MACRO param
param TEXTEQU <How are you>
ENDM
TextVar TEXTEQU <Hello>
TestMacro TextVar
%echo TextVar
輸出的結果是How are you。可見所謂的參數不過就是替換。參數名會被引數名給替換(引數就是調用時候傳遞過去的那些參數)。由於MASM中宏這個系統中,所有的這些變數名的符號都是在一個共同的空間之中(呵呵,怎麼聽起來像數學術語?),都是全域的東西。
對於參數可以進行一些限定修飾,比如讓你調用的時候一定要傳遞這個參數:
Clear_reg MACRO reg:REQ
xor reg, reg
ENDM
或者指定一個預設值:
Clear_reg MACRO reg:=<eax>
xor reg, reg
ENDM
或者讓參數個數成為一個變數。
Clear_reg MACRO regs:VARARG
FOR reg, <regs>
xor reg, reg
ENDM
ENDM
不過要注意的是VARARG修飾的參數必須是參數中的最後一個。
讓宏返回一個值
宏過程和宏函數的區別在於是否有傳回值。當然這裡的傳回值和執行期的函數的傳回值也是很不一樣的。執行期的函數是通過eax來傳遞傳回值的。而這裡,也不過是直接替換而已。傳回值的文法是這樣的:
EXITM textitem
一個宏函數可以有多個EXITM,就像C中的函數可以有多個return一樣。不過必須傳回值一致。看一個簡單的例子
Who MACRO
EXITM <taowen>
ENDM
%echo Who()
結果是顯示taowen。如果把()去掉,則顯示的是who。可見對於宏函數的調用一定要加上 ()。而調用宏過程則不能加()。看一個有趣的例子:
Who MACRO temp
%echo temp
ENDM
Who()
顯示的結果是()。說明()被當作傳遞給宏過程的參數了。
可以比較隨意的使用傳回值,可以把傳回值這麼用。
Who MACRO
EXITM <taowen>
ENDM
Who() TEXTEQU <genius>
這樣就定義了彙編期文本變數,值為genius。可見宏函數可以用在任何文本變數可以出現的地方,很多地方可以把進階語言中函數中那些類推過來。
局部變數
宏中可以有局部變數,它看起來像局部的,實際上不過是一些名稱上的小技巧。
對於局部變數有兩點事實:1、在函數外無法訪問,2、在對函數的不同次的調用中其值應該不受前次調用的影響。
TestMacro MACRO
LOCAL LocalVar
%echo LocalVar
LocalVar TEXTEQU <Hello>
ENDM
如果對於函數的調用每次之間會互相影響,那麼這麼調用:
TestMacro
TestMacro
第一次會產生一個未定義變數的錯誤,而第二次就會輸出Hello。事實上,由於LocalVar 是LOCAL的。所以兩次都是未定義錯誤。這個就體現了局部變數的多次調用的獨立性。
下面我們來揭穿局部變數的底牌。不用我多敘述,直接看看這個你就明白了:
TestMacro MACRO
LOCAL LocalVar
echo LocalVar
ENDM
TestMacro
TestMacro
輸出的結果是:??0000與??0001。這個就是局部變數的實際名字。局部變數就是通過怪怪的名字讓外部無法訪問(你不知道它是什麼名字),然後在每次展開同一個宏的時候用不同的名字替換局部變數的名字,使得多次調用之間不會互相影響。
其實你可以實驗一下這個:
??0000 TEXTEQU <Hello>
TestMacro MACRO
LOCAL LocalVar
%echo LocalVar
ENDM
TestMacro
顯示的結果是Hello,這樣就在一個宏過程(函數)的外部存取了局部變數。
文本操作
MASM內建了兩套文本操作功能,一個是宏函數,另一個是Directive。功能是一樣的,但是提供了表達上的靈活性。
name CATSTR [[textitem1 [[, textitem2]] ...]]
name INSTR [[position,]] textitem1, textitem2
name SIZESTR textitem
name SUBSTR textitem, position [[, length]]
這一套是Directive,作用分別是:串連文本,尋找子文本,獲得文本長度,取子文本。
@CatStr( string1 [[, string2...]] )
@InStr( [[position]], string1, string2 )
@SizeStr( string )
@SubStr( string, position [[, length]] )
這一套是宏函數。用一個例子顯示兩套其實是一樣的:
taowen TEXTEQU @CatStr(<He is >, <genius>)
%echo taowen
taowen CATSTR <He is >, <genius>
%echo taowen
可以看到兩個例子輸出的都是He is genius。有兩點需要注意:1、@CatStr這樣的宏函數可以作為左值,被用來賦值。2、@CatStr這樣的宏函數對於參數並不自動求值,當行為和你想的不一樣的時候,加上%。
對於具體的使用不是很難,實驗一下就可以知道了。一點就是第一個字元的索引是1,而不是C中的0。
%與求值
%可能是最難使用的文法了。一般就是行為和你想的不一樣的時候,加上%實驗一下。%與 <>與!等,構成了一團糟。
一般情況下,你不用%號。用%可以把數值變數轉變為文本變數。可以用%號強製取出文本的值。比如:
Index = 0
NameT CATSTR <Person>, %Index
%NameT TEXTEQU <taowen>
%echo Person0
先對Index用%就是把數值變成文本,第二個%就是把NameT變成Person0。這裡也示範了一個產生變數名的很重要的技巧,只要把Index進行一些遞增,就能夠構建一個變數的數組了。
<>可以一定程度上放置被求值,不過大部分情況下由於文本宏的替換不受影響,所以仍然取到的是替換後的值。!用來取消符號原有的意思。比如:
Symbol CATSTR <Go >, <!,>, <Go>
%echo Symbol
輸出的結果是Go, Go。如果不加!則,號會導致錯誤。有趣的是如果你在第二個Go後面加上 !本來應該是Go, Go!。結果確實一個缺少右角括弧的錯誤,原來是!把>的原有意思變化了,不再表示結束了。如果你這樣:
Symbol CATSTR <Go >, <!,>, <Go!>>
%echo Symbol
得出的就是Go, Go>。看出來是怎麼回事了吧。要產生!,就這麼寫:
Symbol CATSTR <Go >, <!,>, <Go!!>
%echo Symbol
關於什麼時候用<>什麼時候不用,我的看法是最好能用<>就用<>。具體為什麼,是因為能夠加大適用範圍。
在宏中進行迴圈
迴圈有四種:WHILE,REPEAT,FOR,FORC。文法如下:
WHILE expression
statements
ENDM
REPEAT expression
statements
ENDM
FOR parameter [[:REQ | :=default]] , <argument [[, argument]]...>
statements
ENDM
FORC parameter, <string>
statements
ENDM
WHILE與REPEAT從用法到效果是一樣的,至少我認為是一樣的。expression要求值為一個數值,可以用EQ(等於),LT(小於)這些判斷Operator來比較數值。
I = 0
WHILE I LT 10
Temp TEXTEQU %I
%echo Temp
I = I + 1
ENDM
輸出的結果就是0一直到9。
FOR與FORC是專門用途的迴圈,一個是用於取得一個參數列表中的各個參數,另一個是逐個取出一個字串中的每個字元。各舉兩個例子就可以明白:
TestMacro MACRO params:VARARG
FOR param, <params>
%echo param
ENDM
ENDM
TestMacro arg1, arg2, arg3
顯示的結果是arg1然後是arg2然後是arg3。
FORC char, <Hello>
%echo char
ENDM
分別顯示的是H和e和l和l和o。
在宏中進行判斷
判斷很簡單了,不過有很多種IF,IFDEF,IFIDN,IFE,IFNDEF,IFDIF。使用上沒有什麼值得注意的都比較簡單。分別是判斷數值,判斷符號是否定義,判斷兩個文本是否一致。
關於宏還有定義在宏中的宏,OPATTR, SIZEOF, LENGTHOF, 等等許多比較進階的東西。不過我相信有前面講述的基礎,這些東西的使用不過是查查手冊的事情。
ps:
轉載一編關於MASM宏的用法。 By kiki(於2004-6-11發表)