寫好C語(yǔ)言,,漂亮的宏定義很重要,使用宏定義可以防止出錯(cuò),,提高可移植性,,可讀性,方便性 等等,。下面列舉一些成熟軟件中常用得宏定義,。。,。,。。,。 1,,防止一個(gè)頭文件被重復(fù)包含 #ifndef COMDEF_H #define COMDEF_H //頭文件內(nèi)容 #endif 2,重新定義一些類型,,防止由于各種平臺(tái)和編譯器的不同,,而產(chǎn)生的類型字節(jié)數(shù)差異,,方便移植。 typedef unsigned char boolean; /* Boolean value type. */ typedef unsigned long int uint32; /* Unsigned 32 bit value */ typedef unsigned short uint16; /* Unsigned 16 bit value */ typedef unsigned char uint8; /* Unsigned 8 bit value */ typedef signed long int int32; /* Signed 32 bit value */ typedef signed short int16; /* Signed 16 bit value */ typedef signed char int8; /* Signed 8 bit value */ //下面的不建議使用 typedef unsigned char byte; /* Unsigned 8 bit value type. */ typedef unsigned short word; /* Unsinged 16 bit value type. */ typedef unsigned long dword; /* Unsigned 32 bit value type. */ typedef unsigned char uint1; /* Unsigned 8 bit value type. */ typedef unsigned short uint2; /* Unsigned 16 bit value type. */ typedef unsigned long uint4; /* Unsigned 32 bit value type. */ typedef signed char int1; /* Signed 8 bit value type. */ typedef signed short int2; /* Signed 16 bit value type. */ typedef long int int4; /* Signed 32 bit value type. */ typedef signed long sint31; /* Signed 32 bit value */ typedef signed short sint15; /* Signed 16 bit value */ typedef signed char sint7; /* Signed 8 bit value */ 3,,得到指定地址上的一個(gè)字節(jié)或字 #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,得到一個(gè)field在結(jié)構(gòu)體(struct)中的偏移量 #define FPOS( type, field ) \ /*lint -e545 */ ( (dword) &(( type *) 0)-> field ) /*lint +e545 */ 6,得到一個(gè)結(jié)構(gòu)體中field所占用的字節(jié)數(shù) #define FSIZ( type, field ) sizeof( ((type *) 0)->field ) 7,,按照LSB格式把兩個(gè)字節(jié)轉(zhuǎn)化為一個(gè)Word #define FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] ) 8,,按照LSB格式把一個(gè)Word轉(zhuǎn)化為兩個(gè)字節(jié) #define FLOPW( ray, val ) \ (ray)[0] = ((val) / 256); \ (ray)[1] = ((val) & 0xFF) 9,得到一個(gè)變量的地址(word寬度) #define B_PTR( var ) ( (byte *) (void *) &(var) ) #define W_PTR( var ) ( (word *) (void *) &(var) ) 10,,得到一個(gè)字的高位和低位字節(jié) #define WORD_LO(***) ((byte) ((word)(***) & 255)) #define WORD_HI(***) ((byte) ((word)(***) >> 8)) 11,,返回一個(gè)比X大的最接近的8的倍數(shù) #define RND8( x ) ((((x) + 7) / 8 ) * 8 ) 12,將一個(gè)字母轉(zhuǎn)換為大寫 #define UPCASE( c ) ( ((c) >= 'a' && (c) <= 'z') ? ((c) - 0x20) : (c) ) 13,,判斷字符是不是10進(jìn)值的數(shù)字 #define DECCHK( c ) ((c) >= '0' && (c) <= '9') 14,,判斷字符是不是16進(jìn)值的數(shù)字 #define HEXCHK( c ) ( ((c) >= '0' && (c) <= '9') ||\ ((c) >= 'A' && (c) <= 'F') ||\ ((c) >= 'a' && (c) <= 'f') ) 15,防止溢出的一個(gè)方法 #define INC_SAT( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val)) 16,,返回?cái)?shù)組元素的個(gè)數(shù) #define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) ) 17,,返回一個(gè)無(wú)符號(hào)數(shù)n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n) #define MOD_BY_POWER_OF_TWO( val, mod_by ) \ ( (dword)(val) & (dword)((mod_by)-1) ) 18,對(duì)于IO空間映射在存儲(chǔ)空間的結(jié)構(gòu),,輸入輸出處理 #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))) [2005-9-9添加] 19,使用一些宏跟蹤調(diào)試 A N S I標(biāo)準(zhǔn)說(shuō)明了五個(gè)預(yù)定義的宏名,。它們是: _ L I N E _ _ F I L E _ _ D A T E _ _ T I M E _ _ S T D C _ 如果編譯不是標(biāo)準(zhǔn)的,則可能僅支持以上宏名中的幾個(gè),,或根本不支持,。記住編譯程序 也許還提供其它預(yù)定義的宏名。 _ L I N E _及_ F I L E _宏指令在有關(guān)# l i n e的部分中已討論,,這里討論其余的宏名,。 _ D AT E _宏指令含有形式為月/日/年的串,表示源文件被翻譯到代碼時(shí)的日期,。 源代碼翻譯到目標(biāo)代碼的時(shí)間作為串包含在_ T I M E _中,。串形式為時(shí):分:秒。 如果實(shí)現(xiàn)是標(biāo)準(zhǔn)的,,則宏_ S T D C _含有十進(jìn)制常量1,。如果它含有任何其它數(shù),則實(shí)現(xiàn)是 非標(biāo)準(zhǔn)的,。 可以定義宏,,例如: 當(dāng)定義了_DEBUG,輸出數(shù)據(jù)信息和所在文件所在行 #ifdef _DEBUG #define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_) #else #define DEBUGMSG(msg,date) #endif 20,,宏定義防止使用是錯(cuò)誤 用小括號(hào)包含,。 例如:#define ADD(a,b) (a+b) 用do{}while(0)語(yǔ)句包含多語(yǔ)句防止錯(cuò)誤 例如:#difne DO(a,b) a+b;\ a++; 應(yīng)用時(shí):if(….) DO(a,b); //產(chǎn)生錯(cuò)誤 else
C語(yǔ)言中如何使用宏
C(和C++)中的宏(Macro)屬于編譯器預(yù)處理的范疇,屬于編譯期概念(而非運(yùn)行期概念)。下面對(duì)常遇到的宏的使用問(wèn)題做了簡(jiǎn)單總結(jié),。 宏使用中的常見(jiàn)的基礎(chǔ)問(wèn)題 #符號(hào)和##符號(hào)的使用 ...符號(hào)的使用 宏的解釋方法 我們能碰到的宏的使用 宏使用中的陷阱 常見(jiàn)的基礎(chǔ)性問(wèn)題:
關(guān)于#和## 在C語(yǔ)言的宏中,,#的功能是將其后面的宏參數(shù)進(jìn)行字符串化操作(Stringfication),簡(jiǎn)單說(shuō)就是在對(duì)它所引用的宏變量通過(guò)替換后在其左右各加上一個(gè)雙引號(hào),。比如下面代碼中的宏: #define WARN_IF(EXP) \ do{ if (EXP) \ fprintf(stderr, "Warning: " #EXP "\n"); } \ while(0) 那么實(shí)際使用中會(huì)出現(xiàn)下面所示的替換過(guò)程: WARN_IF (divider == 0); 被替換為 do { if (divider == 0) fprintf(stderr, "Warning" "divider == 0" "\n"); } while(0); 這樣每次divider(除數(shù))為0的時(shí)候便會(huì)在標(biāo)準(zhǔn)錯(cuò)誤流上輸出一個(gè)提示信息,。 而##被稱為連接符(concatenator),用來(lái)將兩個(gè)Token連接為一個(gè)Token,。注意這里連接的對(duì)象是Token就行,,而不一定是宏的變量。比如你要做一個(gè)菜單項(xiàng)命令名和函數(shù)指針組成的結(jié)構(gòu)體的數(shù)組,,并且希望在函數(shù)名和菜單項(xiàng)命令名之間有直觀的,、名字上的關(guān)系。那么下面的代碼就非常實(shí)用: struct command { char * name; void (*function) (void); }; #define COMMAND(NAME) { NAME, NAME ## _command } // 然后你就用一些預(yù)先定義好的命令來(lái)方便的初始化一個(gè)command結(jié)構(gòu)的數(shù)組了: struct command commands[] = { COMMAND(quit), COMMAND(help), ... } COMMAND宏在這里充當(dāng)一個(gè)代碼生成器的作用,,這樣可以在一定程度上減少代碼密度,,間接地也可以減少不留心所造成的錯(cuò)誤。我們還可以n個(gè)##符號(hào)連接 n+1個(gè)Token,,這個(gè)特性也是#符號(hào)所不具備的,。比如: #define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d typedef struct _record_type LINK_MULTIPLE(name,company,position,salary); // 這里這個(gè)語(yǔ)句將展開(kāi)為: // typedef struct _record_type name_company_position_salary;
關(guān)于...的使用 ...在C宏中稱為Variadic Macro,也就是變參宏,。比如: #define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__) // 或者 #define myprintf(templt,args...) fprintf(stderr,templt,args) 第一個(gè)宏中由于沒(méi)有對(duì)變參起名,我們用默認(rèn)的宏__VA_ARGS__來(lái)替代它,。第二個(gè)宏中,,我們顯式地命名變參為args,那么我們?cè)诤甓x中就可以用args來(lái)代指變參了,。同C語(yǔ)言的stdcall一樣,,變參必須作為參數(shù)表的最有一項(xiàng)出現(xiàn)。當(dāng)上面的宏中我們只能提供第一個(gè)參數(shù)templt時(shí),,C標(biāo)準(zhǔn)要求我們必須寫成: myprintf(templt,); 的形式,。這時(shí)的替換過(guò)程為: myprintf("Error!\n",); 替換為: fprintf(stderr,"Error!\n",); 這是一個(gè)語(yǔ)法錯(cuò)誤,不能正常編譯,。這個(gè)問(wèn)題一般有兩個(gè)解決方法,。首先,GNU CPP提供的解決方法允許上面的宏調(diào)用寫成: myprintf(templt); 而它將會(huì)被通過(guò)替換變成: fprintf(stderr,"Error!\n",); 很明顯,,這里仍然會(huì)產(chǎn)生編譯錯(cuò)誤(非本例的某些情況下不會(huì)產(chǎn)生編譯錯(cuò)誤),。除了這種方式外,c99和GNU CPP都支持下面的宏定義方式: #define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__) 這時(shí),,##這個(gè)連接符號(hào)充當(dāng)?shù)淖饔镁褪钱?dāng)__VAR_ARGS__為空的時(shí)候,,消除前面的那個(gè)逗號(hào)。那么此時(shí)的翻譯過(guò)程如下: myprintf(templt); 被轉(zhuǎn)化為: fprintf(stderr,templt); 這樣如果templt合法,將不會(huì)產(chǎn)生編譯錯(cuò)誤,。 宏是如何解釋的 宏在日常編程中的常見(jiàn)使用 宏使用中的陷阱 這里列出了一些宏使用中容易出錯(cuò)的地方,,以及合適的使用方式。 錯(cuò)誤的嵌套-Misnesting 宏的定義不一定要有完整的,、配對(duì)的括號(hào),,但是為了避免出錯(cuò)并且提高可讀性,最好避免這樣使用,。 由操作符優(yōu)先級(jí)引起的問(wèn)題-Operator Precedence Problem 由于宏只是簡(jiǎn)單的替換,,宏的參數(shù)如果是復(fù)合結(jié)構(gòu),那么通過(guò)替換之后可能由于各個(gè)參數(shù)之間的操作符優(yōu)先級(jí)高于單個(gè)參數(shù)內(nèi)部各部分之間相互作用的操作符優(yōu)先級(jí),,如果我們不用括號(hào)保護(hù)各個(gè)宏參數(shù),,可能會(huì)產(chǎn)生預(yù)想不到的情形。比如: #define ceil_div(x, y) (x + y - 1) / y 那么 a = ceil_div( b & c, sizeof(int) ); 將被轉(zhuǎn)化為: a = ( b & c + sizeof(int) - 1) / sizeof(int); // 由于+/-的優(yōu)先級(jí)高于&的優(yōu)先級(jí),,那么上面式子等同于: a = ( b & (c + sizeof(int) - 1)) / sizeof(int); 這顯然不是調(diào)用者的初衷,。為了避免這種情況發(fā)生,應(yīng)當(dāng)多寫幾個(gè)括號(hào): define ceil_div(x, y) (((x) + (y) - 1) / (y)) 消除多余的分號(hào)-Semicolon Swallowing 通常情況下,,為了使函數(shù)模樣的宏在表面上看起來(lái)像一個(gè)通常的C語(yǔ)言調(diào)用一樣,,通常情況下我們?cè)诤甑暮竺婕由弦粋€(gè)分號(hào),比如下面的帶參宏: MY_MACRO(x); 但是如果是下面的情況: #define MY_MACRO(x) { \ /* line 1 */ \ /* line 2 */ \ /* line 3 */ } //... if (condition()) MY_MACRO(a); else {...} 這樣會(huì)由于多出的那個(gè)分號(hào)產(chǎn)生編譯錯(cuò)誤,。為了避免這種情況出現(xiàn)同時(shí)保持MY_MACRO(x);的這種寫法,,我們需要把宏定義為這種形式: #define MY_MACRO(x) do { /* line 1 */ \ /* line 2 */ \ /* line 3 */ } while(0) 這樣只要保證總是使用分號(hào),就不會(huì)有任何問(wèn)題,。 Duplication of Side Effects 這里的Side Effect是指宏在展開(kāi)的時(shí)候?qū)ζ鋮?shù)可能進(jìn)行多次Evaluation(也就是取值),,但是如果這個(gè)宏參數(shù)是一個(gè)函數(shù),那么就有可能被調(diào)用多次從而達(dá)到不一致的結(jié)果,,甚至?xí)l(fā)生更嚴(yán)重的錯(cuò)誤,。比如: #define min(X,Y) ((X) > (Y) ? (Y) : (X)) //... c = min(a,foo(b)); 這時(shí)foo()函數(shù)就被調(diào)用了兩次。為了解決這個(gè)潛在的問(wèn)題,,我們應(yīng)當(dāng)這樣寫min(X,Y)這個(gè)宏: #define min(X,Y) ({ \ typeof (X) x_ = (X); \ typeof (Y) y_ = (Y); \ (x_ < y_) ? x_ : y_; }) ({...})的作用是將內(nèi)部的幾條語(yǔ)句中最后一條的值返回,,它也允許在內(nèi)部聲明變量(因?yàn)樗ㄟ^(guò)大括號(hào)組成了一個(gè)局部Scope)。
|