Clang 宏定義初探(一)
宏的定義方法是
#define
那么在什么場景下需要用到宏呢?遇到一些重復(fù)的東西,簡單的有
for(i = 0; i < n; i ++) 之類的,為了減少繁瑣的編碼,可能使用
#define FO(i,N) for(i=0;i<N;i++)
為了增強(qiáng)可讀性,比如說設(shè)置一個(gè)數(shù)組常亮大小,可以使用
#define N 1001
宏看起來感覺很好用,但是潛藏了很多問題,在實(shí)際使用中需要小心謹(jǐn)慎(當(dāng)然帶來大部分問題的,還是編碼者自己或者合作方)。
例如,現(xiàn)在要求兩個(gè)數(shù)的最小值,最初會(huì)寫出如下宏:
#define MIN(A,B) A<B?A:B
正常情況下也是可以使用無誤的:
1 #include <stdio.h>
2 #define MIN(A,B) A<B?A:B
3 int main()
4 {
5 int a =5, b =6;
6 printf("%d", MIN(a, b));
7 }
然而,當(dāng)你發(fā)布你的代碼或者很久以后自己再去調(diào)用時(shí),可能寫成:
MIN(a<4?a:5, b)
看起來也沒啥問題,實(shí)際執(zhí)行一下發(fā)現(xiàn),結(jié)果是5,偏離預(yù)期!事實(shí)上,宏即使單純的代碼展開,當(dāng)你展開上面的式子之后會(huì)發(fā)現(xiàn),實(shí)際執(zhí)行的代碼是
a<4?a:5<b?a<4?a:5:b
對于一直用括號(hào)來解決優(yōu)先級問題的我來說,這種展開完全無法理解,于是參看了內(nèi)核代碼,發(fā)現(xiàn)更安全的寫法為:
#define MIN(A,B) (A)<(B)?(A):(B)
這種寫法規(guī)避了令人厭惡的優(yōu)先級問題!本以為這樣就完成了一個(gè)安全的宏定義,但是事實(shí)上還有其他問題!
float a = 1.0f;
float b = MIN(a++, 1.5f);
printf("a=%f, b=%f",a,b);
神秘的a++,同樣按照剛才的思路展開,因?yàn)楹昀锩鏁?huì)有兩次A的展開,所以a++將被執(zhí)行兩次,再次偏離預(yù)期。對于這種情況,我們需要用到一個(gè)GNU C的賦值擴(kuò)展,即使用({...})的形式。這種形式的語句可以類似shell,在順次執(zhí)行之后,會(huì)將最后一次的表達(dá)式的賦值作為返回。舉個(gè)簡單的例子,下面的代碼執(zhí)行完畢后a的值為3,而且b和c只存在于大括號(hào)限定的代碼域中:
int a = ({
int b = 1;
int c = 2;
b + c;
});
結(jié)果是// => a is 3
基于這種特性,我們可以在宏里面為每個(gè)傳入的參數(shù)進(jìn)行一個(gè)拷貝,然后再對拷貝后的參數(shù)進(jìn)行實(shí)際的比較運(yùn)算,那么最終實(shí)現(xiàn)了一個(gè)比較安全的最小值比較的宏定義:
#define min(x,y) ({ \
typeof(x) __min1 = (x); \
typeof(y) __min2 = (y); \
(void) (& __min1 == & __min2); \
__min1 < __min2 ? __min1 :min2})
最后這一步跳躍有點(diǎn)大了,首先是 typeof 作用是得到參數(shù)的類型,其次是(void) (& __min1 == & __min2); 這個(gè)神秘的寫法是為了驗(yàn)證二者的類型是否一致,當(dāng)然外側(cè)我們確實(shí)用了GNU的擴(kuò)展({...})。
寫到這里,涉及到了另外兩個(gè)問題,GNU擴(kuò)展是啥?另外我們用宏是為了節(jié)省代碼,同時(shí)為了省去一些小函數(shù)的多次重復(fù)調(diào)用的參數(shù)入棧降低性能的問題,那么內(nèi)聯(lián)函數(shù)也有這樣的效果,什么是內(nèi)聯(lián)函數(shù)呢?