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(xxx)   ((byte) ((word)(xxx) & 255))

#define   WORD_HI(xxx)   ((byte) ((word)(xxx) >> 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)))

[2005-9-9添加]

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

        

解決方法: #difne DO(a,b) do{a+b;\

                    a++;}while(0)


宏中"#"和"##"的用法
一、一般用法
我們使用#把宏參數變爲一個字符串,用##把兩個宏參數貼合在一起.
用法:
#i nclude<cstdio>
#i nclude<climits>
using namespace std;

#define STR(s)      #s
#define CONS(a,b)   int(a##e##b)

int main()
{
     printf(STR(vck));            // 輸出字符串"vck"
     printf("%d\n", CONS(2,3));   // 2e3 輸出:2000
     return 0;
}

二、當宏參數是另一個宏的時候
需要注意的是凡宏定義裏有用'#'或'##'的地方宏參數是不會再展開.

1, 非'#'和'##'的情況
#define TOW       (2)
#define MUL(a,b) (a*b)

printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));
這行的宏會被展開爲:
printf("%d*%d=%d\n", (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: %s\n",   STR(INT_MAX));     // INT_MAX #i nclude<climits>
這行會被展開爲:
printf("int max: %s\n", "INT_MAX");

printf("%s\n", CONS(A, A));                // compile error
這一行則是:
printf("%s\n", 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: %s\n", STR(INT_MAX));           // INT_MAX,int型的最大值,爲一個變量 #i nclude<climits>
輸出爲: int max: 0x7fffffff
STR(INT_MAX) -->   _STR(0x7fffffff) 然後再轉換成字符串;

printf("%d\n", 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];
 
//*************************** 典例 ************************** 
#include<stdio.h> 
#define VALUE(s)   printf("%s: %d\n", #s, s)
#define CONS(a,b) (int)a##e##b
#define CONNECT(a, b) a##b
 
int main()
{
    CONNECT(in, t) a = 100;
    VALUE(a);           // 輸出 a: 100
    printf("%d\n", CONS(2, 3)); // 2e3 輸出:2000
 
     printf("%s, %s, %d, %s, %s, %d\n", __FILE__,__func__,__LINE__,__DATE__,__TIME__,__STDC__); 
//日期和時間是編譯時的時間,和運行時無關,所以用處不大,沒有重新編譯,每次運行打印的時間都是一樣的 
    return 0;
}
 
打印出一個宏被展開後的樣子:
#define TO_STRING( x ) TO_STRING1( x )
#define TO_STRING1( x ) #x

TO_STRING首先會將x全部展開(如果x也是一個宏的話),然後再傳給TO_STRING1轉換爲字符串 
  
  
關於#和##
在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)。
 
補充:
1、#define 
命令#define定義了一個標識符及一個串。在源程序中每次遇到該標識符時,均以定義的串代換它。ANSI標準將標識符定義爲宏名,將替換過程稱爲宏 
替換。命令的一般形式爲: 
#define identifier string 
注意: 
? 該語句沒有分號。在標識符和串之間可以有任意個空格,串一旦開始,僅由一新行結束。 
? 宏名定義後,即可成爲其它宏名定義中的一部分。 
? 宏替換僅僅是以文本串代替宏標識符,前提是宏標識符必須獨立的識別出來,否則不進行替換。例如: #define XYZ 
this is a test,使用宏printf("XYZ");//該段不打印"this is a test"而打印"XYZ"。因爲預編譯器識 
別出的是"XYZ" 
? 如果串長於一行,可以在該行末尾用一反斜槓' \'續行。 

2、#error 
處理器命令#error強迫編譯程序停止編譯,主要用於程序調試。 

3、#i nclude 
命令#i nclude使編譯程序將另一源文件嵌入帶有#i nclude的源文件,被讀入的源文件必須用雙引號或尖括號括起來。例如: 
#i nclude"stdio.h"或者#i nclude 
這兩行代碼均使用C編譯程序讀入並編譯用於處理磁盤文件庫的子程序。 
將文件嵌入#i nclude命令中的文件內是可行的,這種方式稱爲嵌套的嵌入文件,嵌套層次依賴於具體實現。 
如果顯式路徑名爲文件標識符的一部分,則僅在哪些子目錄中搜索被嵌入文件。否則,如果文件名用雙引號括起來,則首先檢索當前工作目錄。如果未發現文件, 
則在命令行中說明的所有目錄中搜索。如果仍未發現文件,則搜索實現時定義的標準目錄。 
如果沒有顯式路徑名且文件名被尖括號括起來,則首先在編譯命令行中的目錄內檢索。 
如果文件沒找到,則檢索標準目錄,不檢索當前工作目錄。
 
4、條件編譯命令 
有幾個命令可對程序源代碼的各部分有選擇地進行編譯,該過程稱爲條件編譯。商業軟件公司廣泛應用條件編譯來提供和維護某一程序的許多顧客版本。 
#if、#else,#elif及#endif 
#if的一般含義是如果#if後面的常量表達式爲true,則編譯它與#endif之間的代碼,否則跳過這些代碼。命令#endif標識一個#if塊的 
結束。 
#if constant-expression 
statement sequence 
#endif 
跟在#if後面的表達式在編譯時求值,因此它必須僅含常量及已定義過的標識符,不可使用變量。表達式不許含有操作符sizeof(sizeof也是編譯 
時求值)。 
#else命令的功能有點象C語言中的else;#else建立另一選擇(在#if失敗的情況下)。 
注意,# else屬於# if塊。 
#elif命令意義與ELSE IF 相同,它形成一個if else-if階梯狀語句,可進行多種編譯選擇。 
#elif 後跟一個常量表達式。如果表達式爲true,則編譯其後的代碼塊,不對其它#elif表達式進行測試。否則,順序測試下一塊。 
#if expression 
statement sequence 
#elif expression1 
statement sequence 
#endif 
在嵌套的條件編譯中#endif、#else或#elif與最近#if或#elif匹配。 
# ifdef 和# ifndef 
條件編譯的另一種方法是用#ifdef與#ifndef命令,它們分別表示"如果有定義"及"如果無定義"。 
# ifdef的一般形式是: 
# ifdef macroname 
statement sequence 
#endif 
#ifdef與#ifndef可以用於#if、#else,#elif語句中,但必須與一個#endif。 
 
5、#undef 
命令#undef 取消其後那個前面已定義過有宏名定義。一般形式爲: 
#undef macroname 
 
6、#line 
命令# line改變__LINE__與__FILE__的內容,它們是在編譯程序中預先定義的標識符。命令的基本形式如下: 
# line number["filename"] 
其中的數字爲任何正整數,可選的文件名爲任意有效文件標識符。行號爲源程序中當前行號,文件名爲源文件的名字。命令# line主要用於調試及其它特殊 
應用。 
注意:在#line後面的數字標識從下一行開始的數字標識。 
 
7、預定義的宏名 
ANSI標準說明了C中的五個預定義的宏名。它們是: 
__LINE__ 
__FILE__ 
__DATE__ 
__TIME__ 
__STDC__ 
如果編譯不是標準的,則可能僅支持以上宏名中的幾個,或根本不支持。記住編譯程序也許還提供其它預定義的宏名。 
__LINE__及__FILE__宏指令在有關# line的部分中已討論,這裏討論其餘的宏名。 
__DATE__宏指令含有形式爲月/日/年的串,表示源文件被翻譯到代碼時的日期。 
源代碼翻譯到目標代碼的時間作爲串包含在__TIME__中。串形式爲時:分:秒。 
如果實現是標準的,則宏__STDC__含有十進制常量1。如果它含有任何其它數,則實現是非標準的。編譯C++程序時,編譯器自動定義了一個預處理名 
字__cplusplus,而編譯標準C時,自動定義名字__STDC__。 
注意:宏名的書寫由標識符與兩邊各二條下劃線構成。
 
8、C、C++宏體中出現的#,#@,## 
宏體中,#的功能是將其後面的宏參數進行字符串化操作(Stringfication),簡單說就是在對它所引用的宏變量通過替換後在其左右各加上一個 
雙引號。 
而##被稱爲連接符(concatenator),用來將兩個Token連接爲一個Token。注意這裏連接的對象是Token就行,而不一定是宏的變 
量。比如你要做一個菜單項命令名和函數指針組成的結構體的數組,並且希望在函數名和菜單項命令名之間有直觀的、名字上的關係。那就可以使用:宏參數## 
固定部分。當然還可以n個##符號連接 n+1個Token,這個特性也是#符號所不具備的。 
#@的功能是將其後面的宏參數進行字符化。 
 
9、C宏中的變參... 
...在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合法,將不會產生編譯錯誤。 
 
10、#pragma的使用【轉載】 
在所有的預處理指令中,#Pragma 指令可能是最複雜的了,它的作用是設定編譯器的狀態或者是指示編譯器完成一些特定的動作。#pragma指令對 
每個編譯器給出了一個方法,在保持與C和C ++語言完全兼容的情況下,給出主機或操作系統專有的特徵。依據定義,編譯指示是機器或操作系統專有的,且 
對於每個編譯器都是不同的。 

其格式一般爲: #Pragma Para,其中Para 爲參數,下面來看一些常用的參數。 

(1)message 參數。 Message 參數是我最喜歡的一個參數,它能夠在編譯信息輸出窗口中輸出相應的信息,這對於源代碼信息的控制是非常 
重要的。其使用方法爲: 
#Pragma message("消息文本") 

當編譯器遇到這條指令時就在編譯輸出窗口中將消息文本打印出來。 
當我們在程序中定義了許多宏來控制源代碼版本的時候,我們自己有可能都會忘記有沒有正確的設置這些宏,此時我們可以用這條指令在編譯的時候就進行檢查。 
假設我們希望判斷自己有沒有在源代碼的什麼地方定義了_X86這個宏可以用下面的方法 

#ifdef _X86 
#Pragma message("_X86 macro activated!") 
#endif 
當我們定義了_X86這個宏以後,應用程序在編譯時就會在編譯輸出窗口裏顯示"_ 
X86 macro activated!"。我們就不會因爲不記得自己定義的一些特定的宏而抓耳撓腮了。 
(2)另一個使用得比較多的pragma參數是code_seg。格式如: 

#pragma code_seg( ["section-name"[,"section-class"] ] ) 

它能夠設置程序中函數代碼存放的代碼段,當我們開發驅動程序的時候就會使用到它。 
(3)#pragma once (比較常用) 

只要在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次,這條指令實際上在VC6中就已經有了,但是考慮到兼容性並沒有太多的使用它。 
(4)#pragma hdrstop表示預編譯頭文件到此爲止,後面的頭文件不進行預編譯。BCB可以預編譯頭文件以加快鏈接的速度,但如果所有頭文 
件都進行預編譯又可能佔太多磁盤空間,所以使用這個選項排除一些頭文件。 
有時單元之間有依賴關係,比如單元A依賴單元B,所以單元B要先於單元A編譯。你可以用#pragma startup指定編譯優先級,如果使用了 
#pragma package(smart_init) ,BCB就會根據優先級的大小先後編譯。 
(5)#pragma resource "*.dfm"表示把*.dfm文件中的資源加入工程。*.dfm中包括窗體、外觀的定義。 
(6)#pragma warning( disable : 4507 34; once : 4385; error : 164 ) 
等價於: 
#pragma warning(disable:4507 34) // 不顯示4507和34號警告信息 
#pragma warning(once:4385) // 4385號警告信息僅報告一次 
#pragma warning(error:164) // 把164號警告信息作爲一個錯誤。 
同時這個pragma warning 也支持如下格式: 
#pragma warning( push [ ,n ] ) 
#pragma warning( pop ) 
這裏n代表一個警告等級(1---4)。 
#pragma warning( push )保存所有警告信息的現有的警告狀態。 
#pragma warning( push, n)保存所有警告信息的現有的警告狀態,並且把全局警告等級設定爲n。 
#pragma warning( pop )向棧中彈出最後一個警告信息,在入棧和出棧之間所作的一切改動取消。例如: 
#pragma warning( push ) 
#pragma warning( disable : 4705 ) 
#pragma warning( disable : 4706 ) 
#pragma warning( disable : 4707 ) 
//....... 
#pragma warning( pop ) 
在這段代碼的最後,重新保存所有的警告信息(包括4705,4706和4707)。 
(7)pragma comment(...) 
該指令將一個註釋記錄放入一個對象文件或可執行文件中。 
常用的lib關鍵字,可以幫我們連入一個庫文件。 

(8)用pragma導出dll中的函數 
傳統的到出 DLL 函數的方法是使用模塊定義文件 (.def),Visual C++ 提供了更簡潔方便的方法,那就 
是"__declspec()"關鍵字後面跟"dllexport",告訴連接去要導出這個函數,例如: 
__declspec(dllexport) int __stdcall MyExportFunction(int iTest); 
把"__declspec(dllexport)"放在函數聲明的最前面,連接生成的 DLL 就會導出函 
數"[email=_MyExportFunction@4]_MyExportFunction@4[/email]"。 
上面的導出函數的名稱也許不是我的希望的,我們希望導出的是原版的"MyExportFunction"。還好,VC 提供了一個預處理指示 
符"#pragma"來指定連接選項 (不僅僅是這一個功能,還有很多指示功能) ,如下: 
#pragma comment(linker,"/EXPORT:MyExportFunction=_MyExportFunction@4") 

這下就天如人願了:)。如果你想指定導出的順序,或者只將函數導出爲序號,沒有 Entryname,這個預處理指示符 (確切地說是連接器) 都能夠 實現,看看 MSDN 的語法說明: 
/EXPORT:entryname[,@ordinal[,NONAME]][,DATA] 
@ordinal 指定順序;NONAME 指定只將函數導出爲序號;DATA 關鍵字指定導出項爲數據項。 
⑨每個編譯程序可以用#pragma指令激活或終止該編譯程序支持的一些編譯功能。例如,對循環優化功能: 
#pragma loop_opt(on) // 激活 
#pragma loop_opt(off) // 終止 
有時,程序中會有些函數會使編譯器發出你熟知而想忽略的警告,如"Parameter xxx is never used in function xxx",可以這樣: 
#pragma warn -100 // Turn off the warning message for warning #100 
int insert_record(REC *r) 
{ /* function body */ } 
#pragma warn +100 // Turn the warning message for warning #100 back on 
函數會產生一條有唯一特徵碼100的警告信息,如此可暫時終止該警告。 
每個編譯器對#pragma的實現不同,在一個編譯器中有效在別的編譯器中幾乎無效。可從編譯器的文檔中查看。 
⑩#pragm pack()的使用 
#pragma pack規定的對齊長度,實際使用的規則是: 
? 結構,聯合,或者類的數據成員,第一個放在偏移爲0的地方,以後每個數據成員的對齊,按照#pragma pack指定的數值和這 
個數據成員自身長度中,比較小的那個進行。 
? 也就是說,當#pragma pack的值等於或超過所有數據成員長度的時候,這個值的大小將不產生任何效果。 
? 而結構整體的對齊,則按照結構體中最大的數據成員 和 #pragma pack指定值之間,較小的那個進行。 
注意:文件使用#pragma pack(n) 改變了缺省設置而不恢復,通常可以使用#pragma pack(push, n)和#pragma 
pack(pop)進行設置與恢復。 
注:關於宏函數的內容在另外的專題。關於宏使用的誤區在描述宏的時候已經在文中提到了,最後再給出一個例子,描述的Side Effect是指宏在展開 
的時候對其參數可能進行多次Evaluation(也就是取值)對程序造成的錯誤影響。 
假設在一個系統中,有一個32b的寄存器(REG)保存狀態,其中高16b表示一種含義,低16b表示另一種含義(這在程序中經常出現)。現在要把高低16b分開,不考慮實際中的特殊要求,將代碼寫成: 
#define High16bit(REG) (REG>>16) 
#define Low16bit(REG) ((REG<<16)>>16) 
對於這種寫法完成的功能在大多數情況是足夠了,這裏不討論。主要談論這種寫法的負面影響,如果在程序中分別在不同的語句中使用High16bit和Low16bit,那麼就可能那就是Side effect,特別寄存器REG是狀態寄存器,他的狀態可能隨時變化,那麼引起的問題就是高低16b根本取的不是同一個時刻狀態寄存器。這種錯誤在程序中找出就比較難了。在這裏我把條件弱化了,試想在一個宏體中,如果對參數多次取值也是可能引起問題,那就更難了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章