C語言巨集定義的幾個坑和特殊用法

2021-10-25 07:14:22 字數 4245 閱讀 1750

巨集僅僅是在c預處理階段的一種文字替換工具,編譯完之後對二進位制**不可見。基本用法如下:

標示符別名

#define buffer_size 1024
預處理階段,foo = (char *) malloc (buffer_size);會被替換成foo = (char *) malloc (1024);

巨集體換行需要在行末加反斜槓\

#define numbers 1, \

2, \

3

預處理階段int x =;會被擴充套件成int x =;

巨集函式

巨集名之後帶括號的巨集被認為是巨集函式。用法和普通函式一樣,只不過在預處理階段,巨集函式會被展開。優點是沒有普通函式儲存暫存器和引數傳遞的開銷,展開後的**有利於cpu cache的利用和指令**,速度快。缺點是可執行**體積大。

#define min(x, y)  ((x) < (y) ? (x) : (y))
y = min(1, 2);會被擴充套件成y = ((1) < (2) ? (1) : (2));

字串化(stringification)

在巨集體中,如果巨集引數前加個#,那麼在巨集體擴充套件的時候,巨集引數會被擴充套件成字串的形式。如:

#define warn_if(exp) \

do \

} while (0)

warn_if (x == 0);會被擴充套件成:

do

\}while(0

);

這種用法可以用在assert中,如果斷言失敗,可以將失敗的語句輸出到反饋資訊中

連線(concatenation)

在巨集體中,如果巨集體所在標示符中有##,那麼在巨集體擴充套件的時候,巨集引數會被直接替換到標示符中。如:

#define command(name)  

struct command

;

在巨集擴充套件的時候

struct command commands=

;

會被擴充套件成:

struct command commands=

,,..

.};

這樣就節省了大量時間,提高效率。

幾個坑語法問題

由於是純文字替換,c預處理器不對巨集體做任何語法檢查,像缺個括號、少個分號神馬的預處理器是不管的。這裡要格外小心,由此可能引出各種奇葩的問題,一下還很難找到根源。

算符優先順序問題

#define multiply(x, y) x * y
multiply(1, 2)沒問題,會正常展開成1 * 2。有問題的是這種表示式multiply(1 + 2, 3),展開後成了1 + 2 * 3,顯然優先順序錯了。在巨集體中,給引用的引數加個括號就能避免這問題。

#define multiply(x, y) (x) * (y)
multiply(1+2, 3)就會被展開成(1 + 2) * (3),優先順序正常了。其實這個問題和下面要說到的某些問題都屬於由於純文字替換而導致的語義破壞問題,要格外小心。

分號吞噬問題

有如下巨集定義:

#define skip_spaces(p, limit)       \

\} \

} \

if

(*p !=0)

skip_spaces (p, lim)

;else..

.

編譯,gcc報error: 『else』 without a previous 『if』。原來這個看似是乙個函式的巨集被展開後是一段大括號括起來的**塊,加上分號之後這個if邏輯塊就結束了,所以編譯器發現這個else沒有對應的if。這個問題一般用do ... while(0)的形式來解決:

#define skip_spaces(p, limit)         \

do \

} \

} while (0)

展開後就成了

if

(*p !=0)

do...

while(0

);else..

.

這樣就消除了分號吞噬問題。這個技巧在linux核心原始碼裡很常見,比如這個置位巨集(位於arch/mips/include/asm/mach-pnx833x/gpio.h)

#define set_reg_bit(reg, bit)  do  while (0)
巨集引數重複呼叫

有如下巨集定義:

#define min(x, y)  ((x) < (y) ? (x) : (y))
當有如下呼叫時next = min (x + y, foo (z));,巨集體被展開成next = ((x + y) < (foo (z)) ? (x + y) : (foo (z)));,可以看到,foo(z)被重複呼叫了兩次,做了重複計算。更嚴重的是,如果foo是不可重入的(foo內修改了全域性或靜態變數),程式會產生邏輯錯誤。所以,盡量不要在巨集引數中傳入函式呼叫。

對自身的遞迴引用

有如下巨集定義:

#define foo (4 + foo)
按前面的理解,(4 + foo)會展開成(4 + (4 + foo)),然後一直展開下去,直至記憶體耗盡。但是,預處理器採取的策略是只展開一次。也就是說,foo只會展開成(4 + foo),而展開之後foo的含義就要根據上下文來確定了。

對於以下的交叉引用,巨集體也只會展開一次。

#define x (4 + y)

#define y (2 * x)

x展開成(4 + y) -> (4 + (2 * x))y展開成(2 * x) -> (2 * (4 + y))。注意,這是極不推薦的寫法,程式可讀性極差。

巨集引數預處理

巨集引數中若包含另外的巨集,那麼巨集引數在被代入到巨集體之前會做一次完全的展開,除非巨集體中含有###。有如下巨集定義:

#define afterx(x) x_ ## x

#define xafterx(x) afterx(x)

#define tablesize 1024

#define bufsize tablesize

afterx(bufsize)會被展開成x_bufsize。因為巨集體中含有##,巨集引數直接代入巨集體。xafterx(bufsize)會被展開成x_1024。因為xafterx(x)的巨集體是afterx(x),並沒有###,所以bufsize在代入前會被完全展開成1024,然後才代入巨集體,變成x_1024

C語言巨集的特殊用法和幾個坑

2 years ago source 總結一下c語言中巨集的一些特殊用法和幾個容易踩的坑。由於本文主要參考gcc文件,某些細節 如巨集引數中的空格是否處理之類 在別的編譯器可能有細微差別,請參考相應文件。巨集僅僅是在c預處理階段的一種文字替換工具,編譯完之後對二進位制 不可見。基本用法如下 1.標示...

C語言特殊巨集定義的使用

1 是將字母變成字串的巨集定義 用法 define n a a printf s n fdaf 輸出為fdaf。不可能做到將乙個變數通過這個操作變成字串 比如int a 5,不能n a 得到 5 得到的只是 a 但是巨集定義的常量可以。需要乙個中間巨集如下 define n a n a define...

c語言解除巨集定義 C語言中巨集定義的用法

說到巨集定義,我們應該先了解什麼是預處理指令,相信大家並不會陌生,之前我們程式設計時,程式的開頭 include指令,define指令都是預處理指令,它能使我們的編譯更加的高效,便捷,因此c語言中是允許使用者自己加入一些特定的預處理指令的。一,不帶引數的巨集定義 不帶引數的巨集定義是比較簡單的,就是...