C語言宏定義運用技巧

來源:互聯網
上載者:User

寫好C語言,好看的宏定義很主要,運用宏定義可以防止 出錯,提高可移植性,可讀性,方便性 等等。下面列舉一些成熟軟體中常用得宏定義……
   1,防止 一個標頭檔被重複包含
   #ifndef COMDEF_H
   #define COMDEF_H
   //標頭檔內容
   #endif
   2,重新定義一些類型,防止 由於各種平台和編譯器的不同,而產生的類型位元組數差異,方便移植。
   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,得到指定地址上的一個位元組或字
   #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 )
   /*lint -e545 */ ( (dword) &(( type *) 0)-> field ) /*lint +e545 */
   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(***) ((byte) ((word)(***) & 255))
   #define WORD_HI(***) ((byte) ((word)(***) >> 8))
       11,返回一個比X大的最接近的8的倍數
   #define RND8( x ) ((((x) + 7) / 8 ) * 8 )
   12,將一個字母轉換為大寫
   #define UPCASE( c ) ( ((c) >= 'a' && (c) <= 'z') ? ((c) - 0x20) : (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,返回一個無符號數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,對於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)))
   19,運用一些宏跟蹤調試
   A N S I標準說明了五個預定義的宏名。它們是:
   _ L I N E _
   _ F I L E _
   _ D A T E _
   _ T I M E _
   _ S T D C _
   如果編譯不是標準的,則可能僅支援以上宏名中的多個,或根本不支援。記得編譯器
   也許還提供其它預定義的宏名。
   _ L I N E _及_ F I L E _巨集指令在有關# l i n e的部分中已討論,這裡討論其餘的宏名。
   _ D AT E _巨集指令含有形式為月/日/年的串,表示源檔案被翻譯到代碼時的日期。
   原始碼翻譯到目標代碼的時間作為串包含在_ T I M E _中。串形式為時:分:秒。
   如果實現是標準的,則宏_ S T D C _含有十進位常量1.如果它含有任何其它數,則實現是非標準的。
   可以定義宏,例如:
   當定義了_DEBUG,輸出資料資訊和所在檔案所在行
   #ifdef _DEBUG
   #define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)
   #else
   #define DEBUGMSG(msg,date)
   #endif
   20,宏定義防止 運用是不正確
   用小括弧包含。
   例如:#define ADD(a,b) (a+b)
   用do{}while(0)語句包含多語句防止 不正確
   例如:#difne DO(a,b) a+b;
   a++;
   運用 時:if(…。)
   DO(a,b); //產生不正確
   else
   C語言中如何 運用宏
   C(和C++)中的宏(Macro)屬於編譯器預先處理的範疇,屬於編譯期概念(而非運行期概念)。下面對常遇到的宏的運用疑問做了基本總結。
   宏運用中的多見的基礎疑問
   #符號和##符號的運用
   ……符號的運用
   宏的解釋要領
   我們能碰到的宏的運用
   宏運用中的陷阱
   多見的基礎性疑問:
   關於#和##
   在C語言的宏中,#的功能是將其後面的宏參數執行 字串化操作(Stringfication),基本說就是在對它所引用的宏變數通過替換後在其左右各加上一個雙引號。比如下面代碼中的宏:
        #define WARN_IF(Exp )    
   do{ if (Exp )     fprintf(stderr, "Warning: " #Exp "n"); }    
   while(0)
   那麼實際運用中會出現下面所示的替換流程:
   WARN_IF (divider == 0);
   被替換為
   do {
   if (divider == 0)
   fprintf(stderr, "Warning" "divider == 0" "n");
   } while(0);
   這樣每次divider(除數)為0的時候便會在標準不正確流上輸出一個提示資訊。
   而##被稱為串連符(concatenator),用來將兩個Token串連為一個Token.留心這裡串連的對象是Token就行,而不一定是宏的變數。比如你要做一個功能表項目命令名和函數指標組成的結構體的數組,並且希望在函數名和功能表項目命令名之間有直觀的、名字上的聯絡。那麼下面的代碼就非常實用:
   struct command
   {
   char * name;
   void (*function) (void);
   };
   #define COMMAND(NAME) { NAME, NAME ## _command }
   // 然後你就用一些預先定義好的命令來方便的原始化一個command結構的數組了:
   struct command commands[] = {
   COMMAND(quit),
   COMMAND(help),
   ……
   } COMMAND宏在這裡充當一個代碼產生器的作用,這樣可以在一定程度上減少代碼密度,間接地也可以減少不留心所造成的不正確。我們還可以n個##符號串連 n+1個Token,這個特徵也是#符號所不具備的。比如:
   #define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
   typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
   // 這裡這個語句將展開為:
   //     typedef struct _record_type name_company_position_salary;
   關於……的運用
   ……在C宏中稱為Variadic Macro,也就是變參宏。比如:
        #define myprintf(templt,……) fprintf(stderr,templt,__VA_ARGS__)
   // 或者
   #define myprintf(templt,args……) fprintf(stderr,templt,args)
   第一個宏中由於沒有對變參起名,我們用預設的宏__VA_ARGS__來替代它。第二個宏中,我們顯式地命名變參為args,那麼我們在宏定義中就可以用args來代指變參了。同C語言的stdcall一樣,變參必須作為參數表的最有一項出現。當上面的宏中我們只能提供第一個參數templt時,C標準要求我們必須寫成:myprintf(templt,);
   的形式。這時的替換流程為:
   myprintf("Error!n",);
   替換為:
   fprintf(stderr,"Error!n",);
   這是一個文法不正確,不能正常編譯。這個疑問一般有兩個處理要領。首先,GNU CPP提供的處理要領允許上面的宏調用寫成:
   myprintf(templt);
   而它將會被通過替換變成:
   fprintf(stderr,"Error!n",);
   很明顯,這裡仍然會產生編譯不正確(非本例的某些情況下不會產生編譯不正確)。除了這種方式外,c99和GNU CPP都支援下面的宏定義方式:
   #define myprintf(templt, ……) fprintf(stderr,templt, ##__VAR_ARGS__)
   這時,##這個串連符號充當的作用就是當__VAR_ARGS__為空白的時候,消除前面的那個逗號。那麼此時的翻譯流程如下:
   myprintf(templt);
   被轉化為:
   fprintf(stderr,templt);
   這樣如果templt正當,將不會產生編譯不正確。
   宏是如何 解釋的
   宏在日常編程中的多見運用
   宏運用中的陷阱
   這裡列出了一些宏運用中容易出錯的地點,以及合適的運用方式。
   不正確的嵌套-Misnesting
   宏的定義不一定要有完整的、配對的括弧,但是為了防止出錯並且提高可讀性,最好防止這樣運用。
   由操作符優先順序引起的疑問-Operator Precedence Problem
   由於宏只是基本的替換,宏的參數如果是複合結構,那麼通過替換之後可能由於各個參數之間的操作符優先順序高於單個參數內部各部分之間相互作用的操作符優先順序,如果我們不用括弧保衛各個宏參數,可能會產生預想不到的情形。比如:
       #define ceil_div(x, y) (x + y - 1) / y
   那麼
   a = ceil_div( b & c, sizeof(int) );
   將被轉化為:
   a = ( b & c     + sizeof(int) - 1) / sizeof(int);
   // 由於+/-的優先順序高於&的優先順序,那麼上面式子等同於:
   a = ( b & (c + sizeof(int) - 1)) / sizeof(int);
   這顯然不是調用者的初衷。為了防止這種情況發生,應當多寫多個括弧:
   define ceil_div(x, y) (((x) + (y) - 1) / (y))
   消除多餘的分號-Semicolon Swallowing
   通常情況下,為了使函數模樣的宏在表面上看起來像一個通常的C語言調用一樣,通常情況下我們在宏的後面加上一個分號,比如下面的帶參宏:
   MY_MACRO(x);
   但是如果是下面的情況:
   #define MY_MACRO(x) {
   /* line 1 */
   /* line 2 */
   /* line 3 */ }
   //……
   if (condition())
   MY_MACRO(a);
   else
   {……}
   這樣會由於多出的那個分號產生編譯不正確。為了防止這種情況出現同時保持MY_MACRO(x);的這種寫法,我們須要把宏定義為這種形式:
   #define MY_MACRO(x) do {
   /* line 1 */
   /* line 2 */
   /* line 3 */ } while(0)
   這樣只要保證總是運用分號,就不會有任何疑問。
   Duplication of Side Effects
   這裡的Side Effect是指宏在展開的時候對其參數可能執行 多次Evaluation(也就是取值),但是如果這個宏參數是一個函數,那麼就有可能被調用多次從而達到不一致的結果,甚至會發生更嚴重的不正確。比如:
   #define min(X,Y) ((X) > (Y) ? (Y) : (X))
   //……
   c = min(a,foo(b));
   這時foo()函數就被調用了兩次。為了處理這個潛在的疑問,我們應當這樣寫min(X,Y)這個宏:
       #define min(X,Y) ({
   typeof (X) x_ = (X);
   typeof (Y) y_ = (Y);
   (x_ < y_) ? x_ : y_; })
   ({……})的作用是將內部的幾條語句中最後一條的值返回,它也允許在內部聲明變數(因為它通過大括弧組成了一個局部Scope)。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.