這篇文章是我花了一個晚上作的總結,從一個比較本質的角度解釋了一些宏的行為,不是教科書設置是Programmer Guide達到的程度??雌饋肀容^沒有味道,但是如果你要用起宏來你會發現我幾乎沒有說廢話。不是教你怎么去用宏,但是你看過之后也不用學了,自己試驗一下就可以寫了。雖然沒有幾個家伙還在用MASM,更少人用MACRO了,可能這個就是我另類的風格吧。
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用作匯編期的數值變量。
-
定義匯編期常量
什么是匯編期常量呢?其實也就是常量的意思,因為無論在編譯期還是執行期它都是靜態的,一旦定義之后其值不能改變?;貞浽贑中,你用#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
用%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??梢姾旰瘮悼梢杂迷谌魏挝谋咀兞靠梢猿霈F的地方,很多地方可以把高級語言中函數中那些類推過來。
局部變量
宏中可以有局部變量,它看起來像局部的,實際上不過是一些名稱上的小技巧。
對于局部變量有兩點事實: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, 等等許多比較高級的東西。不過我相信有前面講述的基礎,這些東西的使用不過是查查手冊的事情。
Good luck, that is all.
浙公網安備 33010602011771號