當然宏定義非常重要的,它可以幫助我們防止出錯,提高代碼的可移植性和可讀性等。
下面列舉一些成熟軟件中常用得宏定義
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 1elsedo_something(); // Branch 2展開后代碼如下:if (x > y)
{ // Single-branch if-statement!!!int tmp; // The one and only branch consiststmp = x; // of the block.x = y;y = tmp;}; // empty statementelse // ERROR!!! “parse error before else”do_something();問題是分號(;)出現在塊后面。解決這個問題可以用do{}while(0):if (x > y)do {int tmp;tmp = x;x = y;y = tmp;} while(0);elsedo_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);elsedoanotherthing();一切看起來很正常,但是仔細想想。這個宏會展開成:if(temp)if(condition) dosomething();elsedoanotherthing();這時的else不是與第一個if語句匹配,而是錯誤的與第二個if語句進行了匹配,編譯通過了,但是運行的結果一定是錯誤的。為了避免這個錯誤,我們使用do{….}while(0) 把它包裹起來,成為一個獨立的語法單元,從而不會與上下文發生混淆。同時因為絕大多數的編譯器都能夠識別do{…}while(0)這種無用的循環并進行優化,所以使用這種方法也不會導致程序的性能降低。
另一個講解這是為了含多條語句的宏的通用性因為默認規則是宏定義最后是不能加分號的,分號是在引用的時候加上的比如定義了一個宏fw(a,b),那么在c文件里一定是這樣引用fw(a,b);如果不用do…while,那么fw就得定義成:#define fw(a,b) {read((a));write((b));}那這樣fw(a,b);展開后就成了:{read(a);write(b);};最后就多了個分號,這是語法錯誤而定義成do…while的話,展開后就是:do{read(a);write(b);}while(0); 完全正確所以要寫一個包含多條語句的宏的話,不用do…while是不可能的
宏中#和##的用法
一、一般用法我們使用#把宏參數變為一個字符串,用##把兩個宏參數貼合在一起.用法:#include<cstdio>#include<climits>using namespace std;
#define STR(s) #s#define CONS(a,b) int(a##e##b)
int main()
{
printf(STR(vck)); // 輸出字符串vckprintf(%dn, CONS(2,3)); // 2e3 輸出:2000return 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: 0x7fffffffSTR(INT_MAX) –> _STR(0x7fffffff) 然后再轉換成字符串;
printf(%dn, CONS(A, A));輸出為:200CONS(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];
新聞熱點
疑難解答