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

C語言宏定義中 # 與 ## 運算符實戰指南:字符串化、Token 拼接與調試宏模板

     編輯:關於C

運算符核心語義

運算符英文名功能作用階段
#Stringification (字符串化)將宏參數轉為字符串字面量宏擴展時
##Concatenation (連接/粘合)將兩側 Token 拼接為單一 Token宏擴展時

基礎演示程序


#include 

void quit_command() { printf("I am quit command\n"); }
void help_command() { printf("I am help command\n"); }

struct command {
    char *name;
    void (*function)(void);
};

// 核心宏:字符串化 name,連接生成函數名
#define COMMAND(NAME) { #NAME, NAME##_command }
#define PRINT(NAME) printf("token" #NAME "=%d\n", token##NAME)

int main() {
    int token9 = 9;
    PRINT(9);  // token9=9

    struct command commands[] = {
        COMMAND(quit),  // {"quit", quit_command}
        COMMAND(help)   // {"help", help_command}
    };
    commands[0].function(); // I am quit command
    return 0;
}

展開過程逐步解析


// PRINT(9)
printf("token" #9 "=%d\n", token##9)
→ printf("token" "9" "=%d\n", token9)  // 字符串字面量自動拼接
→ printf("token9=%d\n", token9)

// COMMAND(quit)
{ #quit, quit##_command }
→ { "quit", quit_command }

進階模式庫

1. 自動註冊命令表(解析器雛形)


#define REG_CMD(name) { #name, cmd_##name }
struct cmd_entry { const char *name; void (*fn)(void); } cmd_table[] = {
    REG_CMD(init),
    REG_CMD(start),
    REG_CMD(stop),
    REG_CMD(status)
};
// 查找執行
void execute(const char *input) {
    for (size_t i=0; i

2. 結構體偏移量計算(Linux 內核 container_of 原理)


#define OFFSETOF(type, member) ((size_t)&(((type*)0)->member))
#define CONTAINER_OF(ptr, type, member) ((type*)((char*)(ptr) - OFFSETOF(type, member)))

3. 編譯期斷言(C11 _Static_assert 前身)


#define COMPILE_ASSERT(cond, msg) typedef char static_assert_##msg[(cond)?1:-1]
COMPILE_ASSERT(sizeof(int) == 4, int_must_be_4_bytes);
// 失敗時:error: size of array 'static_assert_int_must_be_4_bytes' is negative

4. 可變參數調試宏(GNU ##__VA_ARGS__ 擴展)


#ifdef DEBUG
#define DBG(fmt, ...) fprintf(stderr, "[DBG %s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#else
#define DBG(fmt, ...) ((void)0)
#endif

DBG("User %s login", name);     // 正常
DBG("Checkpoint");              // 無參數也合法(## 吞噬逗號)

兩層宏展開技巧(必須掌握)

當參數本身為宏時,#/## 旁邊不自動展開:


#define VAL 42
#define STR(s) #s
#define STR2(s) STR(s)

STR(VAL)   → "VAL"     (參數未展開)
STR2(VAL)  → STR(42) → "42" (兩層強制展開)

常見陷阱避坑表

陷阱錯誤代碼正確代碼原因
尾隨逗號#define M(...) f(__VA_ARGS__)#define M(...) f(##__VA_ARGS__)GNU擴展,空參數吞噬逗號
參數未展開STR(MACRO)STR2(MACRO)兩層宏強制展開
非法Token拼接INT##_MAX直接寫 INT_MAX拼接結果必須合法
副作用重複#define SQUARE(x) x*x內聯函數或 ({typeof(x)_x=x;_x*_x;})參數多次出現導致多次求值
字符串化空白STR( a ) → " a "調用時不加多餘空格參數原樣保留空白

最佳實踐總結

  1. 宏非函數:無類型檢查、無作用域、參數多次展開、調試困難。
  2. 能用 static inline 就不用宏:類型安全、可調試、零開銷(優化後)。
  3. 必須用宏的場景:__FILE__/__LINE__ 捕獲、Token 拼接生成標識符、字符串化參數名、編譯期斷言、可變參數日志。
  4. 規範命名:宏全大寫,內部宏加 _ 前綴(_STR、_CONCAT)。
  5. 文檔化副作用:註釋明確標註參數求值次數、是否修改參數。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved