C語言中如何使用巨集

2021-08-25 04:06:54 字數 3800 閱讀 5402

c(和c++)中的巨集(macro)屬於編譯器預處理的範疇,屬於編譯期概念(而非執行期概念)。下面對常遇到的巨集的使用問題做了簡單總結。

巨集使用中的常見的基礎問題

#符號和##符號的使用

...符號的使用

巨集的解釋方法

我們能碰到的巨集的使用

巨集使用中的陷阱

常見的基礎性問題

關於#和##

在c語言的巨集中,#的功能是將其後面的巨集引數進行字串化操作(stringfication),簡單說就是在對它所引用的巨集變數通過替換後在其左右各加上乙個雙引號。比如下面**中的巨集:

#define warn_if(exp)      \

do     \

while(0)

那麼實際使用中會出現下面所示的替換過程:

warn_if (divider == 0);

被替換為

do while(0);

這樣每次divider(除數)為0的時候便會在標準錯誤流上輸出乙個提示資訊。

而##被稱為連線符(concatenator),用來將兩個token連線為乙個token。注意這裡連線的物件是token就行,而不一定是巨集的變數。比如你要做乙個選單項命令名和函式指標組成的結構體的陣列,並且希望在函式名和選單項命令名之間有直觀的、名字上的關係。那麼下面的**就非常實用:

struct command

;#define command(name)

// 然後你就用一些預先定義好的命令來方便的初始化乙個command結構的陣列了:

struct command commands =

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)

//...

if (condition())

my_macro(a);

else

這樣會由於多出的那個分號產生編譯錯誤。為了避免這種情況出現同時保持my_macro(x);的這種寫法,我們需要把巨集定義為這種形式:

#define my_macro(x) do 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) ()

()的作用是將內部的幾條語句中最後一條的值返回,它也允許在內部宣告變數(因為它通過大括號組成了乙個區域性scope)。

C語言中的巨集

c語言中如何使用巨集c 和c 中的巨集 macro 屬於編譯器預處理的範疇,屬於編譯期概念 而非執行期概念 下面對常遇到的巨集的使用問題做了簡單總結。在c語言的巨集中,的功能是將其後面的巨集引數進行字串化操作 stringfication 簡單說就是在對它所引用的巨集變數通過替換後在其左右各加上乙個...

c語言中關於巨集

我們在寫c語言程式中,已經初步了解到了 define的用法,下面對 define做乙個詳細的用法說明。格式如下 define name stuff有了這條指令之後,每當有name出現,就會被預處理器替換為stuff。例 define reg register define do forever fo...

c語言中的「巨集」

簡單來說 巨集定義又稱為巨集代換 巨集替換,簡稱 巨集 是c提供的三種預處理功能的其中一種。複雜的請看下面,講的很全。下面的帶參巨集定義,多行巨集定義,在linux核心原始碼中很多。另外sizeof也是乙個巨集定義。巨集定義巨集定義是c提供的三種預處理功能的其中一種,這三種預處理包括 巨集定義 檔案...