?? 匯009.txt
字號:
了解的內容:在源程序中運用宏的好處,宏嵌套定義的含義和應用方法。知道各種條件匯編偽指令對生成代碼的影響。
掌握的內容:宏和子程序的區別,宏的參數和子程序的參數在處理上差異。宏參數中各種特殊運算符的含義和作用。
熟練掌握的內容:宏定義的一般方法,宏引用及其參數傳遞方法。宏偽指令Local和重復匯編偽指令——REPT、IRP和IRPC——的作用。
建議學習時間:8小時。
第9章 宏
宏是程序設計的另一個基本概念,它是把一段程序代碼用一個特定標識符(即:宏名)來表示。這樣,在編寫源程序時,程序員就可以直接使用該標識符來代替一段代碼的編寫,從而減少了重復代碼的編寫工作,也為減少錯誤,提高程序的可維護性提供了幫助。宏在高級語言(如:C/C++等)也有廣泛的使用。
9.1 宏的定義和引用
通常情況下,宏是用來代表一個具有特定功能的程序段,它只需在源程序中定義一次,但可在源程序中引用多次。只要在編寫程序時需要,就可以直接使用它。
9.1.1 宏的定義
在使用宏之前必須先定義宏,定義宏一般格式如下:
宏名 MACRO [形參1, 形參2, ……]
… ;宏的定義體
ENDM
在書寫宏定義時,必須遵照下列規定:
、MACRO和ENDM是二個必須成對出現的關鍵字,它們分別表示宏定義的開始和結束;
、MACRO和ENDM之間的部分是宏的定義體,它是由指令、偽指令或引用其它宏所組成的程序片段,是宏所包含的具體內容;
、“宏名”是由程序員指定的一個合法的標識符,它代表該宏;
、宏名可以與指令助憶符、偽指令名相同。在這種情況下,宏指令優先,而同名的指令或偽指令都失效;
、在ENDM的前面不要再寫一次宏名,這與段或子程序定義的結束方式有所不同;
、在宏定義的首部可以列舉若干個形式參數,每個參數之間要用逗號分隔。
根據上述規定,我們提倡宏名盡可能不要與指令助憶符、偽指令相名,以免引起不必要的誤會。
例9.1 定義一個把16位數據寄存器壓棧的宏。
PUSHR MACRO
PUSH AX
PUSH BX
PUSH CX
PUSH DX
ENDM
例9.2 定義二個字存儲變量相加的宏。
MADDM MACRO OPRD1, OPRD2
MOV AX, OPRD2
ADD OPRD1, AX
ENDM
上述宏定義雖然能滿足題目的要求,但由于在定義體中改變了寄存器AX的值,這就使宏的引用產生了一定的副作用。為了使寄存器AX的使用變得透明,可把該宏定義改成如下形式:
MADDM MACRO OPRD1, OPRD2
PUSH AX
MOV AX, OPRD2
ADD OPRD1, AX
POP AX
ENDM
通過在宏定義的開始和結尾分別增加對所用寄存器的保護和恢復指令,就使得:對該宏的任意引用都不會產生任何副作用。
9.1.2 宏的引用
在源程序中,一旦定義了某宏,那么,在該程序的任何位置都可直接引用該宏,而不必重復編寫相應的程序段。引用宏的一般格式如下:
宏名 [實參1, 實參2, ……]
其中:實參的位置要與形參的位置要對應,但實參的個數可以與形參的個數不相等。
、當實參的個數多于形參的個數時,多出的實參被忽略;
、當實參的個數少于形參的個數時,沒有實參對應的形參用“空”來對應。但在宏展開時,所得到的指令必須是合法的匯編指令,否則,匯編程序將會給出出錯信息。
例如:假設已有字變量w1和w2,并且也有例9.2中的宏MADDM,那么,如果要把w2的內容加到w1中的話,就可以在源程序的代碼段中按下列方式來引用該宏(點擊它,可顯示宏展開后的形式):
MADDM w1, w2 1 MOV AX, w2
1 ADD w1, AX
注意:以上的宏展開形式可以在LST文件中看到,宏展開所得到的語句前面有一個數字(如:1),它表示宏展開的層次。
如果在源程序中按下列方式來引用了宏MADDM,那么,在宏展開時就會得到下列語句:
MADDM w1 1 MOV AX, ;顯然,指令“MOV AX”是一條非法指令,匯編程序將會指出該錯誤。
1 ADD w1, AX
9.1.3 宏的參數傳遞方式
在引用宏時,參數是通過“實參”替換“形參”的方式來實現傳遞的。參數的形式靈活多樣,參數可以是常數、寄存器、存儲單元和表達式,還可以是指令的操作碼。
例9.3 定義二個字存儲變量相加和相減的宏。
解:
方法1:定義二個宏分別實現存儲變量的加操作和減操作
方法2:定義一個宏,把存儲變量的加和減操作合并在一起
MADDM MACRO OPRD1, OPRD2
MOV AX, OPRD2
ADD OPRD1, AX
ENDM
MSUBM MACRO OPRD1, OPRD2
MOV AX, OPRD2
SUB OPRD1, AX
ENDM
MOPM MACRO OP, OPRD1, OPRD2
MOV AX, OPRD2
OP OPRD1, AX
ENDM
其中:參數OP是一個對應于操作碼的形式參數。
下面是引用宏MOPM的二個例子,它們在宏展開時將會得到下列語句:
MOPM SUB, w1, w2
1 MOV AX, w2
1 SUB w1, AX
…
MOPM ADD, w1, w2
1 MOV AX, w2
1 ADD w1, AX
9.1.4 宏的嵌套定義
宏的嵌套定義有二種方式:宏定義體內引用其它的宏和宏定義體內定義其它的宏。
1、宏定義體內引用其它的宏
在宏的定義體中又引用了其它已定義好的宏,這種宏定義方式在實際的編程過程時常會用到。如果被引用的宏還沒定義的話,匯編程序將會顯示出錯信息。
例如:
ABS MACRO OPRD1, OPRD2
…
MOPM SUB, OPRD1, OPRD2 ;引用前面已定義的宏MOPM
…
ENDM
在定義宏ABS時,引用了前面已定義好的宏MOPM。
2、宏定義體內定義其它的宏
宏的定義體內又定義了其它宏,只有在先引用了外層的宏定義,才能引用內層的宏,這是因為,當外層宏展開后,內層宏的定義才變得有效。
這種定義方式雖然有其好的一面,但它使宏的定義更加隱蔽,削弱了程序的可讀性,降低了程序的可維護性,因此,這種宏定義方式在實際的編程中很少使用。
例9.4 假設有三個內存字變量Oprd1,Oprd2和Oprd3,編寫宏定義實現下面功能(其中:OP是除加、減之外的二元操作):
Oprd1 ← Oprd2 OP Oprd3
解:
方法1:用宏嵌套定義的方式來實現
OPMM MACRO NAME, OP
NAME MACRO OPRD1, OPRD2, OPRD3
PUSH AX
MOV AX, OPRD2
OP AX, OPRD3
MOV OPRD1, AX
POP AX
ENDM
ENDM
這時,如果要完成下列操作(其中:w1,w2和w3是內存字變量):
w1 ← w2 + w3 w1 ← w2 OR w3
那么,可按下列宏引用的方式來完成所需要的功能:
OPMM MADDM, ADD
1 MADDM MACRO OPN1, OPN2, OPN3
1 PUSH AX
1 MOV AX, OPN2
1 ADD AX, OPN3
1 MOV OPN1, AX
1 POP AX
1 ENDM ;宏引用語句(1):在宏擴展后將得到一個新的宏定義MADDM——把后二個內存字變量之和賦給第一個內存字變量:
OPMM MORM, OR
1 MORM MACRO OPN1, OPN2, OPN3
1 PUSH AX
1 MOV AX, OPN2
1 OR AX, OPN3
1 MOV OPN1, AX
1 POP AX
1 ENDM ;宏引用語句(2):在宏擴展后將得到另一個新的宏定義MORM——把后二個內存字變量之“邏輯或”賦給第一個內存字變量:
MADDM w1, w2, w3
1 PUSH AX
1 MOV AX, W2
1 OR AX, W3
1 MOV W1, AX
1 POP AX
1 ENDM ;有了宏引用語句(1)和(2)所得到的宏定義,當前宏引用語句和下一條才有效,并在宏擴展時能得到相應的程序片段。
MORM w1, w2, w3
1 PUSH AX
1 MOV AX, W2
1 OR AX, W3
1 MOV W1, AX
1 POP AX
1 ENDM
方法2:把所要運算的指令操作符作為參數進行傳遞
OPMM MACRO OP, OPRD1, OPRD2, OPRD3
PUSH AX
MOV AX, OPRD2
OP AX, OPRD3
MOV OPRD1, AX
POP AX
ENDM
有了上述定義之后,可按下列宏引用的方式來完成上面的“加”和“邏輯或”的功能,單擊它們可顯示宏擴展時所得到的程序片段:
OPMM ADD, w1, w2, w3
1 PUSH AX
1 MOV AX, w2
1 ADD AX, w3
1 MOV w1, AX
1 POP AX
OPMM OR, w1, w2, w3
1 PUSH AX
1 MOV AX, w2
1 OR AX, w3
1 MOV w1, AX
1 POP AX
從上面二種方法的比較來看,方法2顯然要簡單、直觀些,程序的可讀性和可維護性也要高一些。所以,通常情況下,宏定義的嵌套方式在實際編程過程中是很少使用的。
9.1.5 宏與子程序的區別
宏和子程序都是為了簡化源程序的編寫,提高程序的可維護性,但是它們二者之間存在著以下本質的區別:
1、在源程序中,通過書寫宏名來引用宏,而子程序是通過CALL指令來調用;
2、匯編程序對宏通過宏擴展來加入其定義體,宏引用多少次,就相應擴展多少次,所以,引用宏不會縮短目標程序;而子程序代碼在目標程序中只出現一次,調用子程序是執行同一程序段,因此,目標程序也得到相應的簡化;
3、宏引用時,參數是通過“實參”替換“形參”的方式來實現傳遞的,參數形式靈活多樣,而子程序調用時,參數是通過寄存器、堆棧或約定存儲單元進行傳遞的;
4、宏引用語句擴展后,目標程序中就不再有宏引用語句,運行時,不會有額外的時間開銷,而子程序的調用在目標程序中仍存在,子程序的調用和返回均需要時間。
總之,當程序片段不長,速度是關鍵因素時,可采用宏來簡化源程序,但當程序片段較長,存儲空間是關鍵因素時,可采用子程序的方法來簡化源程序和目標程序。
9.2 宏參數的特殊運算符
為了宏定義和引用的某些特殊需要,匯編程序還支持幾個具體特定含義的運算符。
9.2.1 連接運算符
在宏定義中,如果形式參數與其它字符連接在一起,或形式參數出現在字符串之中,那么,就必須使用連接運算符(&)。
例9.5 定義一個轉移宏JUMP,其一個參數決定轉移類別,另一個參數指定轉移目標。
解:
JUMP MACRO CON, here
J&CON here
ENDM
假設存在下面二個引用語句,那么,點擊它們可得到擴展后的指令。
JUMP mp, next
1 Jmp next
…
JUMP nz, next1
1 Jnz next1
例9.6 定義一個問候性的字符串宏GREETING,其一個參數說明字符串的變量名,另一個參數指名問候的對象。
解:
GREETING MACRO MSG, name
MSG DB 'Hello, &name'
ENDM
假設有下面引用語句,那么,點擊它們將會擴展得到三個問候性的字符串定義。
GREETING STR1, 張三
1 STR1 DB 'Hello, 張三'
…
GREETING STR2, 李四
1 STR2 DB 'Hello, 李四'
…
GREETING MSG1, John
1 MSG1 DB 'Hello, John'
9.2.2 字符串整體傳遞運算符
字符串整體傳遞運算符是一對尖括號<>,用它括起來的內容將作為一個字符串來進行形式參數的整體替換。在宏引用時,如果實參內包含逗號、空格等間隔符,則必須使用該操作符,以保證實參的完整性。如果實參是某個具有特殊含義的字符,為了使它只表示該字符本身,也需要用該運算符括起來。
假設有下面定義字符串的宏DEFMSG,
DEFMSG MACRO MSG
DB '&MSG', 0DH, 0AH, '$'
ENDM
那么,使用和不使用該運算符的引用宏及其宏擴展如下所示:
DEFMSG <Are you ready?>
1 DB 'Are you ready?', 0DH, 0AH, '$'
…
DEFMSG Are you ready?
1 DB 'Are', 0DH, 0AH, '$'
9.2.3 字符轉義運算符
在引用宏時,如果實參中含有特殊字符,而又要該特殊字符當作普通字符來出來,那么,就必須在該特殊字符前加上字符轉義運算符“!”。
下面不使用和使用字符轉義運算符的宏引用語句及其宏擴展的結果:
DEFMSG <Input one number(>90):>
1 DB 'Input one number(90):', 0DH, 0AH, '$'
;第一個“>”與字符“<”相比配,而不會把它當作“大于號”字符來處理
…
DEFMSG <Input one number(!>90):>
1 DB 'Input one number(>90):', 0DH, 0AH, '$'
;由于在第一個“>”字符前面有字符轉義運算符“!”,所以,匯編程序會把第一個“>”當作“大于號”字符來處理,而把最后面的字符“>”當作是與前面“<”相比配的結束符。
9.2.4 計算表達式運算符
在引用宏時,使用計算表達式運算符“%”表示把其后面表達式的結果當作實參進行替換,而不是該表達式的整個式子。
下面使用和不使用計算表達式運算符的宏引用語句及其宏擴展的結果:
DEFMSG %200+23-100
1 DB '123', 0DH, 0AH, '$' ;先計算出表達式200+23-100的值,然后再把該值作為參數進行替換
…
DEFMSG 200+23-100
1 DB '200+23-100', 0DH, 0AH, '$' ;把整個表達式200+23-100當作一個字符串來進行參數替換
9.3 與宏有關的偽指令
在宏定義時,為了滿足某種特殊需要,匯編語言還提供了幾個偽指令。
9.3.1 局部標號偽指令LOCAL
在宏定義體中,如果存在標號,則該標號要用偽指令LOCAL說明為局部標號,否則,當在源程序中,有多于一次引用該宏時,匯編程序在進行宏擴展后將會給出:標號重復定義的錯誤。
偽指令LOCAL的一般格式如下:
LOCAL 標號1, 標號2, ……
偽指令LOCAL必須是偽指令MACRO后的第一條語句,并且在MACRO和LOCAL之間也不允許有注釋和分號標志。
匯編程序在每次進行宏擴展時,總是把由LOCAL說明的標號用一個唯一的符號(從??0000到??FFFF)來代替,從而避免標號重定義的錯誤。
例9.7 編寫求一個求絕對值的宏。
解:
方法1:
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -