來源於microchip資深工程師的網路研討會
關於C的預先處理和它對宏定義的預先處理,我們來看一些宏的正確和錯誤的拓展,以及怎樣拓展他們,所有的規範都是建立在ANSI C編譯器基礎上的。
1. 簡單的宏處理:
#define name “replacement text”
樣本用法:
#define START 20
foobar = START;
“replacement text”可以被其他的宏定義展開
例如:
#define LIMIT 100
#define PREF LIMIT
setting = PREF;
這種宏展開可以拓展至沒有宏定義為止
2. 防止重複定義的宏
例如:
#ifndef LCDH
// header file contents go here
// now stop this being included again
#define LCDH
#endif
此種使用的作用範圍是從定義到模組結束,或者遇到#undef
3.帶參數的宏定義
#define name(argument-list) replacement text
參數會替換掉“replacement text”裡的對應項
樣本用法:
#define DIFF(a, b) ((a)>=(b) ? (a)-(b) : -1)
result = DIFF(input, 6);
展開後是:
result = ((input)>=(6) ? (input)-(6) : -1);
前置處理器能夠替換一個或者更多的變數參數,實現更加複雜的替換。為了防止產生非預料的結果,每個替換的變數都要加上括弧。
4.字串替換
例如:
#define PRODUCT(year) "Matrix" #year
const char * productName = PRODUCT(2000);
展開後:
const char * productName = "Matrix" "2000";
最終變成:
const char * productName = "Matrix2000";
當宏定義中有變數參數時,你就能夠使用一些特殊的操作符。其中一個就是#操作符,它可以把變數轉換成為c字串。在這個例子裡,經過預先處理,變數productName將會指向字串“Matrix2000”,在擴充中,兩個符號#year被變數替換,並且產生的字串和“Matrix”串連。
5.串連變數
例如:
#define BLATCH(bit) LATB##bit
BLATCH(4) = 1;
替換成為:
LATB4 = 1;
另一個宏操作符就是“##”,可以讓你串連任意的兩個符號,而不僅僅是字串。在這個例子中,操作符##用來串連符號LATB和bit變數。當產生替換時,它就會擴充成LATB4,這就是PIC處理器的latch B的第四位連接埠。
這個功能很有用,但是在變數本身是其他需要前置處理器擴充的宏時,會有一個潛在的問題,我們看下面:
#define BLATCH(bit) LATB##bit
#define MOTOR 4
BLATCH(MOTOR) = 1;
經過前置處理器
會擴充成為:LATBMOTOR = 1;
這顯然不是我門想要的結果
問題的根源是,當宏變數進行替換時,宏變數不會被優先擴充。前置處理器的定義是,當只有宏變數不在#,##之前或者不在##之後的時候才能被擴充,所以當“MOTOR插入到“BLATCH”中時,他不會被進一步擴充,因為它臨近著##操作符。
幸運的時,我們找到了一個方法實現這種擴充:
可以分兩步,來進行:
#define PASTE(a, b) a##b
#define BLATCH(bit) PASTE(LATB, bit)
#define MOTOR 4
BLATCH(MOTOR) = 1;
經過前置處理器:
LATB4 = 1;
第二個宏實現變數的擴充,第二個宏實現了串連,這就實現了我們要的功能。
我們來看最後一個複雜一些的例子:
#define PASTE2(a,b) a##b
#define PASTE(a,b) PASTE2(a,b)
#ifdef PIC18 // set for PIC18 devices
#define IO LAT
#else
#define IO PORT
#endif
#define PIN 4
#define RDPORT PASTE(PORT, PIN)
#define RDLATCH PASTE(IO, PIN)
#define IOREAD(loc) PASTE(RD, loc)
使用:
result = IOREAD(LATCH);
當我們看懂這個例子的時候,會覺得很有成就感吧,但當你認為這段代碼會工作的很順利時,你將會發現編譯器錯誤或者其他的不良後果。
最後的結果是:result = PASTE(LAT, 4);
並不是我們想要的,究其原因就是每個宏擴充在一個運算式裡只能實現一次,具體過程是這樣的:
IOREAD(LATCH)
-> PASTE(RD, LATCH)
-> RDLATCH
-> PASTE(IO, PIN)
-> PASTE(LAT, 4); ✘
在進行PASTE第二次擴充時,前置處理器不會進行擴充。
解決辦法就是另外再寫一個,和PASTE同樣功能,但是名字不一養的宏來使用。