首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C語言宏定義完全指南:符號常量、類函數宏、#與##運算符、do-while(0)技巧

     編輯:關於C語言

C 預處理器宏定義深度解析:從基礎到內核級技巧

宏定義是 C 語言預處理階段的文本替換機制,靈活強大但暗藏陷阱。本文系統梳理符號常量、類函數宏、字符串化/粘合運算符,並解析 Linux 內核經典的 do { ... } while(0) 包裹技巧。

一、符號常量(類對象宏)

#define PI 3.1415926
#define BUFFER_SIZE 4096
  • 優勢:集中維護常量,修改一處全局生效;無類型開銷,編碼期替換。
  • 規則:宏名通常大寫;主體可為任意符號序列(常量、表達式、語句);行尾用 \ 換行;主體中宏名會遞歸展開,但被雙引號包裹則不展開。

二、類函數宏(帶參數宏)與參數副作用陷阱

#define SQUARE(x) ((x) * (x))  // 正確寫法:參數與表達式雙重加括號

經典陷阱:

#define BAD_SQUARE(x) x * x
int a = BAD_SQUARE(3 + 4);  // 展開為 3 + 4 * 3 + 4 = 19 ❌
int b = SQUARE(3 + 4);      // 展開為 ((3 + 4) * (3 + 4)) = 49 ✅

鐵律:宏定義中每個參數用 () 包裹,整個表達式再用 () 包裹,防止運算符優先級導致邏輯錯誤。

三、# 字符串化運算符與 ## 粘合運算符

應用場景:

  • #:調試打印宏 #define LOG(x) printf(#x " = %d\\n", x)。
  • ##:自動生成變量/函數名、構造結構體字段訪問器、X Macro 技巧。

四、多語句宏包裹:為什麼用 do { ... } while(0)?

Linux 內核 include/linux/pm.h 經典模式:

#define device_init_wakeup(dev, val) \
    do { \
        device_can_wakeup(dev) = !!(val); \
        device_set_wakeup_enable(dev, val); \
    } while(0)

對比三種寫法:

  1. 不包裹:if (cond) macro(); 只有第一條語句受 if 控制,其餘語句無條件執行。
  2. { ... } 包裹:if (cond) macro(); else ... 因 ; 導致 if 提前結束,else 無法匹配,語法錯誤。
  3. do { ... } while(0):恰好執行一次,語法上是單條語句,可跟 ;,完美配合 if/else、循環體等所有上下文。

原理:do-while 是退出條件循環,體至少執行一次;條件為 0 保證僅執行一次。編譯器會優化掉循環結構,零開銷。

五、宏定義最佳實踐清單

  • ✅ 宏名全大寫,避免與標識符衝突。
  • ✅ 參數與表達式雙重加括號:((x) + (y))。
  • ✅ 多語句必用 do { ... } while(0) 包裹。
  • ✅ 複雜邏輯優先用 inline 函數(類型安全、可調試、無副作用)。
  • ❌ 避免宏中修改參數(如 #define INC(x) ++x 導致 INC(i++) 未定義行為)。
  • ❌ 避免長宏:超過 3-5 行改用函數,利用編譯器內聯優化。

宏是 C 語言「元編程」的基石,用好它能極大提升代碼復用與可讀性;濫用它則製造難以調試的災難。牢記「參數加括號、語句用 do-while、優先考慮 inline」,方得宏之真髓。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
運算符作用示例展開結果
#將參數轉為字符串字面量#define STR(x) #x
STR(hello)
"hello"
##連接兩個符號為單一符號#define VAR(n) var_##n
VAR(1)
var_1