1. 程式人生 > >C語言~巨集操作大全(巨集定義、內建巨集、__FILE__、__LINE__、##用法)

C語言~巨集操作大全(巨集定義、內建巨集、__FILE__、__LINE__、##用法)

 當然巨集定義非常重要的,它可以幫助我們防止出錯,提高程式碼的可移植性和可讀性等。

下面列舉一些成熟軟體中常用得巨集定義

1,防止一個頭檔案被重複包含
#ifndef COMDEF_H
#define COMDEF_H

//標頭檔案內容 …
#endif

2,重新定義一些型別,防止由於各種平臺和編譯器的不同,而產生的型別位元組數差異,方便移植。
typedef  unsigned long int  uint32;      /* Unsigned 32 bit value */

3,得到指定地址上的一個位元組或字
#define  MEM_B( x )  ( *( (byte *) (x) ) )
#define MEM_W( x ) ( *( (word *) (x) ) ) 4,求最大值和最小值 #define MAX( x, y ) ( ((x) > (y)) ? (x) : (y) ) #define MIN( x, y ) ( ((x) < (y)) ? (x) : (y) ) 5,得到一個field在結構體(struct)中的偏移量 #define FPOS( type, field ) ( (dword) &(( type *) 0)-> field ) 6,得到一個結構體中field所佔用的位元組數 #define FSIZ( type, field ) sizeof( ((type *) 0)->field )
7,按照LSB格式把兩個位元組轉化為一個word #define FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] ) 8,按照LSB格式把一個word轉化為兩個位元組 #define FLOPW( ray, val ) (ray)[0] = ((val) / 256); (ray)[1] = ((val) & 0xFF) 9,得到一個變數的地址(word寬度) #define B_PTR( var ) ( (byte *) (void *) &(var) ) #define W_PTR( var ) ( (word *) (void *) &(var) )
10,得到一個字的高位和低位位元組 #define WORD_LO(xxx) ((byte) ((word)(var) & 255)) #define WORD_HI(xxx) ((byte) ((word)(var) >> 8)) 11,返回一個比X大的最接近的8的倍數 #define RND8( x ) ((((x) + 7) / 8 ) * 8 ) 12,將一個字母轉換為大寫 #define UPCASE( c ) ( ((c) >= ’a' && (c) <= ’z') ? ((c) - 0×20) : (c) ) 13,判斷字元是不是10進值的數字 #define DECCHK( c ) ((c) >= ’0′ && (c) <= ’9′) 14,判斷字元是不是16進值的數字 #define HEXCHK( c ) ( ((c) >= ’0′ && (c) <= ’9′) || ((c) >= ’A' && (c) <= ’F') || ((c) >= ’a' && (c) <= ’f') ) 15,防止溢位的一個方法 #define INC_SAT( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val)) 16,返回陣列元素的個數 #define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) ) 17,對於IO空間對映在儲存空間的結構,輸入輸出處理 #define inp(port) (*((volatile byte *) (port))) #define inpw(port) (*((volatile word *) (port))) #define inpdw(port) (*((volatile dword *)(port))) #define outp(port, val) (*((volatile byte *) (port)) = ((byte) (val))) #define outpw(port, val) (*((volatile word *) (port)) = ((word) (val))) #define outpdw(port, val) (*((volatile dword *) (port)) = ((dword) (val))) 18,使用一些巨集跟蹤除錯 ANSI標準說明了五個預定義的巨集名。它們是: __LINE__ __FILE__ __DATE__ __TIME__ __STDC__ 如果編譯不是標準的,則可能僅支援以上巨集名中的幾個,或根本不支援。記住編譯程式 也許還提供其它預定義的巨集名。 是行連線符,會將下一行和前一行連線成為一行,即將物理上的兩行連線成邏輯上的一行 __FILE__ 是內建巨集 代表原始檔的檔名 __LINE__ 是內建巨集,代表該行程式碼的所在行號 __DATE__巨集指令含有形式為月/日/年的串,表示原始檔被翻譯到程式碼時的日期。 原始碼翻譯到目的碼的時間作為串包含在__TIME__ 中。串形式為時:分:秒。 如果實現是標準的,則巨集__STDC__含有十進位制常量1。如果它含有任何其它數,則實現是非標準的。 可以定義巨集,例如: 當定義了_DEBUG,輸出資料資訊和所在檔案所在行 #ifdef _DEBUG #define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_) #else #define DEBUGMSG(msg,date) #endif 19,巨集定義防止使用是錯誤 用小括號包含。 例如:#define ADD(a,b) (a+b)do{}while(0)語句包含多語句防止錯誤 例如:#difne DO(a,b) a+b; a++; 應用時:if(….) DO(a,b); //產生錯誤 else 解決方法: #difne DO(a,b) do{a+b; a++;}while(0) 為什麼需要do{…}while(0)形式? 總結了以下幾個原因: 1),空的巨集定義避免warning: #define foo() do{}while(0) 2),存在一個獨立的block,可以用來進行變數定義,進行比較複雜的實現。 3),如果出現在判斷語句過後的巨集,這樣可以保證作為一個整體來是實現: #define foo(x) action1(); action2(); 在以下情況下: if(NULL == pPointer) foo(); 就會出現action2必然被執行的情況,而這顯然不是程式設計的目的。 4),以上的第3種情況用單獨的{}也可以實現,但是為什麼一定要一個do{}while(0)呢, 看以下程式碼: #define switch(x,y) {int tmp; tmp=x;x=y;y=tmp;} if(x>y) switch(x,y); else //error, parse error before else otheraction(); 在把巨集引入程式碼中,會多出一個分號,從而會報錯。 使用do{….}while(0) 把它包裹起來,成為一個獨立的語法單元,從而不會與上下文發生混淆。 同時因為絕大多數的編譯器都能夠識別do{…}while(0)這種無用的迴圈並進行優化, 所以使用這種方法也不會導致程式的效能降低。 為什麼很多linux核心中巨集#defines用do { … } while(0)? 有很多原因: (Dave Miller的說法): 編譯器對於空語句會給出告警,這是為什麼#define FOO do{ }while(0); 給定一個基本塊(區域性可視域),定義很多區域性變數; (Ben Collins的說法): 在條件程式碼中,允許定義複雜的巨集。可以想像有很多行巨集,如下程式碼 #define FOO(x) printf(“arg is %sn”, x); do_something_useful(x); 現在,想像下面的應用: if (blah == 2) FOO(blah); 展開後代碼為: if (blah == 2) printf(“arg is %sn”, blah); do_something_useful(blah);; 就像你看到的,if僅僅包含了printf(),而do_something_useful()呼叫是無條件呼叫。 因此,如果用do { … } while(0),結果是: if (blah == 2) do { printf(“arg is %sn”, blah); do_something_useful(blah); } while (0); 這才是所期望的結果。 (Per Persson的說法): 像 Miller and Collins指出的那樣,需要一個塊語句包含多個程式碼行和宣告區域性變數。 但是,本質如下面例子程式碼: #define exch(x,y) { int tmp; tmp=x; x=y; y=tmp; } 上面程式碼在有些時候卻不能有效工作,下面程式碼是一個有兩個分支的if語句: if (x > y) exch(x,y); // Branch 1 else do_something(); // Branch 2 展開後代碼如下: if (x > y) { // Single-branch if-statement!!! int tmp; // The one and only branch consists tmp = x; // of the block. x = y; y = tmp; } ; // empty statement else // ERROR!!! “parse error before else” do_something(); 問題是分號(;)出現在塊後面。解決這個問題可以用do{}while0): if (x > y) do { int tmp; tmp = x; x = y; y = tmp; } while(0); else do_something(); ( Bart Trojanowski的說法): Gcc加入了語句解釋,它提供了一個替代do-while-0塊的方法。 對於上面的解決方法如下,並且更加符合常理 #define FOO(arg) ({ typeof(arg) lcl; lcl = bar(arg); lcl; }) 這是一個奇怪的迴圈,它根本就只會執行一次,為什麼不去掉外面的do{..}while結構呢? 我曾一度在心裡把它叫做“怪圈”。原來這也是非常巧妙的技巧。在工程中可能經常會引起麻煩, 而上面的定義能夠保證這些麻煩不會出現。 下面是解釋: 假設有這樣一個巨集定義 #define macro(condition) if(condition) dosomething() 現在在程式中這樣使用這個巨集: if(temp) macro(i); else doanotherthing(); 一切看起來很正常,但是仔細想想。這個巨集會展開成: if(temp) if(condition) dosomething(); else doanotherthing(); 這時的else不是與第一個if語句匹配,而是錯誤的與第二個if語句進行了匹配,編譯通過了, 但是執行的結果一定是錯誤的。為了避免這個錯誤,我們使用do{….}while(0) 把它包裹起來, 成為一個獨立的語法單元,從而不會與上下文發生混淆。同時因為絕大多數的編譯器都能夠識別do{…}while(0) 這種無用的迴圈並進行優化,所以使用這種方法也不會導致程式的效能降低。 另一個講解 這是為了含多條語句的巨集的通用性 因為預設規則是巨集定義最後是不能加分號的,分號是在引用的時候加上的 比如定義了一個巨集fw(a,b),那麼在c檔案裡一定是這樣引用 fw(a,b); 如果不用dowhile,那麼fw就得定義成: #define fw(a,b) {read((a));write((b));} 那這樣fw(a,b);展開後就成了: {read(a);write(b);}; 最後就多了個分號,這是語法錯誤 而定義成dowhile的話,展開後就是: do{read(a);write(b);}while(0); 完全正確 所以要寫一個包含多條語句的巨集的話,不用dowhile是不可能的 巨集中#和##的用法 一、一般用法 我們使用#把巨集引數變為一個字串,用##把兩個巨集引數貼合在一起. 用法: #include<cstdio> #include<climits> using namespace std; #define STR(s) #s #define CONS(a,b) int(a##e##b) int main() { printf(STR(vck)); // 輸出字串vck printf(%dn, CONS(2,3)); // 2e3 輸出:2000 return 0; } 二、當巨集引數是另一個巨集的時候 需要注意的是凡巨集定義裡有用’#'或’##’的地方巨集引數是不會再展開. 1, 非’#'和’##’的情況 #define TOW (2) #define MUL(a,b) (a*b) printf(%d*%d=%dn, TOW, TOW, MUL(TOW,TOW)); 這行的巨集會被展開為: printf(%d*%d=%dn, (2), (2), ((2)*(2))); MUL裡的引數TOW會被展開為(2). 2, 當有’#'或’##’的時候 #define A (2) #define STR(s) #s #define CONS(a,b) int(a##e##b) printf(“int max: %sn”, STR(INT_MAX)); // INT_MAX #include<climits> 這行會被展開為: printf(“int max: %sn”, #INT_MAX); printf(%sn, CONS(A, A)); // compile error 這一行則是: printf(%sn, int(AeA)); INT_MAX和A都不會再被展開, 然而解決這個問題的方法很簡單. 加多一層中間轉換巨集. 加這層巨集的用意是把所有巨集的引數在這層裡全部展開, 那麼在轉換巨集裡的那一個巨集(_STR)就能得到正確的巨集引數. #define A (2) #define _STR(s) #s #define STR(s) _STR(s) // 轉換巨集 #define _CONS(a,b) int(a##e##b) #define CONS(a,b) _CONS(a,b) // 轉換巨集 printf(int max: %sn, STR(INT_MAX)); //INT_MAX,int型的最大值,為一個變數 #include<climits> 輸出為: int max: 0x7fffffff STR(INT_MAX) –> _STR(0x7fffffff) 然後再轉換成字串; printf(%dn, CONS(A, A)); 輸出為:200 CONS(A, A) –> _CONS((2), (2)) –> int((2)e(2)) 三、’#'和’##’的一些應用特例 1、合併匿名變數名 #define __ANONYMOUS1(type, var, line) type var##line #define _ANONYMOUS0(type, line) __ANONYMOUS1(type, _anonymous, line) #define ANONYMOUS(type) _ANONYMOUS0(type, __LINE__) 例: ANONYMOUS(static int); 即: static int _anonymous70; 70表示該行行號; 第一層:ANONYMOUS(static int); –> __ANONYMOUS0(static int, __LINE__); 第二層:–> ___ANONYMOUS1(static int, _anonymous, 70); 第三層:–> static int _anonymous70; 即每次只能解開當前層的巨集,所以__LINE__在第二層才能被解開; 2、填充結構 #define FILL(a) {a, #a} enum IDD{OPEN, CLOSE}; typedef struct MSG{ IDD id; const char * msg; }MSG; MSG _msg[] = {FILL(OPEN), FILL(CLOSE)}; 相當於: MSG _msg[] = {{OPEN, “OPEN”}, {CLOSE, ”CLOSE“}}; 3、記錄檔名 #define _GET_FILE_NAME(f) #f #define GET_FILE_NAME(f) _GET_FILE_NAME(f) static char FILE_NAME[] = GET_FILE_NAME(__FILE__); 4、得到一個數值型別所對應的字串緩衝大小 #define _TYPE_BUF_SIZE(type) sizeof #type #define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type) char buf[TYPE_BUF_SIZE(INT_MAX)]; –> char buf[_TYPE_BUF_SIZE(0x7fffffff)]; –> char buf[sizeof 0x7fffffff]; 這裡相當於: char buf[11];