名稱空間
變體
操作

替換文字宏

來自 cppreference.com

預處理器支援文字宏替換和函式式文字宏替換。

目錄

[編輯] 語法

#define 識別符號 替換列表 (可選) (1)
#define 識別符號 ( 引數 ) 替換列表 (2)
#define 識別符號 ( 引數, ... ) 替換列表 (3) (C99 起)
#define 識別符號 ( ... ) 替換列表 (4) (C99 起)
#undef 識別符號 (5)

[編輯] 解釋

[編輯] #define 指令

#define 指令將 識別符號 定義為宏,即它們指示編譯器將所有後續出現的 識別符號 替換為 替換列表,該列表可以選擇性地進行額外處理。如果識別符號已被定義為任何型別的宏,除非定義完全相同,否則程式格式不正確。

[編輯] 類物件宏

類物件宏將每個已定義的 識別符號 出現替換為 替換列表#define 指令的版本 (1) 的行為正是如此。

[編輯] 類函式宏

類函式宏將每個已定義的 識別符號 出現替換為 替換列表,此外還會接受多個引數,這些引數隨後替換 替換列表 中任何 引數 的相應出現。

類函式宏呼叫的語法類似於函式呼叫的語法:宏名稱的每個例項後面緊跟一個 ( 作為下一個預處理標記,都會引入被替換列表替換的標記序列。該序列由匹配的 ) 標記終止,並跳過中間匹配的左右括號對。

引數的數量必須與宏定義中的引數數量 (parameters) 相同,否則程式格式不正確。如果識別符號不是函式表示法,即其後沒有括號,則完全不會被替換。

#define 指令的版本 (2) 定義了一個簡單的類函式宏。

#define 指令的版本 (3) 定義了一個帶有可變數量引數的類函式宏。可以使用 __VA_ARGS__ 識別符號訪問附加引數,該識別符號隨後被替換為提供給要替換的識別符號的引數。

#define 指令的版本 (4) 定義了一個帶有可變數量引數但沒有常規引數的類函式宏。只能使用 __VA_ARGS__ 識別符號訪問引數,該識別符號隨後被替換為提供給要替換的識別符號的引數。

對於版本 (3,4),替換列表 可能包含標記序列 __VA_OPT__ ( 內容 ),如果 __VA_ARGS__ 非空,則替換為 內容,否則展開為空。

#define F(...) f(0 __VA_OPT__(,) __VA_ARGS__)
F(a, b, c) // replaced by f(0, a, b, c)
F()        // replaced by f(0)
 
#define G(X, ...) f(0, X __VA_OPT__(,) __VA_ARGS__)
G(a, b, c) // replaced by f(0, a, b, c)
G(a, )     // replaced by f(0, a)
G(a)       // replaced by f(0, a)
 
#define SDEF(sname, ...) S sname __VA_OPT__(= { __VA_ARGS__ })
SDEF(foo);       // replaced by S foo;
SDEF(bar, 1, 2); // replaced by S bar = { 1, 2 };
(自 C23 起)


注意:如果類函式宏的引數包含未受匹配的左右括號對保護的逗號(例如 macro(array[x = y, x + 1])atomic_store (p, (struct S){ a, b });),則逗號被解釋為宏引數分隔符,導致由於引數數量不匹配而編譯失敗。

[編輯] ### 運算子

在類函式宏中,替換列表 中識別符號前的 # 運算子會對識別符號進行引數替換,並將結果用引號括起來,有效地建立一個字串字面量。此外,預處理器會新增反斜槓以轉義嵌入式字串字面量(如果有)周圍的引號,並根據需要將字串中的反斜槓加倍。所有前導和尾隨空格都將被刪除,並且文字中間的任何空格序列(但不包括嵌入式字串字面量內部)都會摺疊成一個空格。此操作稱為“字串化”。如果字串化的結果不是有效的字串字面量,則行為未定義。

# 出現在 __VA_ARGS__ 之前時,整個展開的 __VA_ARGS__ 將被引號括起來。

#define showlist(...) puts(#__VA_ARGS__)
showlist();            // expands to puts("")
showlist(1, "x", int); // expands to puts("1, \"x\", int")
(C99 起)

替換列表 中任意兩個連續識別符號之間的 ## 運算子會對這兩個識別符號進行引數替換,然後將結果連線起來。此操作稱為“連線”或“標記貼上”。只有能夠一起形成有效標記的標記才能被貼上:形成更長識別符號的識別符號,形成數字的數字,或形成 += 的運算子 +=。不能透過貼上 /* 來建立註釋,因為註釋在考慮宏替換之前已從文字中刪除。如果連線結果不是有效的標記,則行為未定義。

注意:一些編譯器提供了一個擴充套件,允許 ## 出現在逗號之後和 __VA_ARGS__ 之前,在這種情況下,當 __VA_ARGS__ 非空時 ## 不做任何事情,但當 __VA_ARGS__ 為空時會刪除逗號:這使得可以定義諸如 fprintf (stderr, format, ##__VA_ARGS__) 的宏。

### 運算子的求值順序未指定。

[編輯] #undef 指令

#undef 指令取消定義 識別符號,即它取消由 #define 指令對 識別符號 的先前定義。如果識別符號沒有關聯的宏,則忽略該指令。

[編輯] 預定義宏

以下宏名稱在任何翻譯單元中預定義

__STDC__
展開為整數常量 1。此宏旨在指示符合標準的實現
(宏常量)
__STDC_VERSION__
(C95)
展開為 long 型別的整數常量,其值隨 C 標準的每個版本而增加
  • 199409L (C95 起)
  • 199901L (C99 起)
  • 201112L (C11 起)
  • 201710L (C17 起)
  • 202311L (C23 起)
    (宏常量)
__STDC_HOSTED__
(C99)
如果實現是宿主的(在作業系統下執行),則展開為整數常量 1;如果實現是獨立的(在沒有作業系統的情況下執行),則展開為 0
(宏常量)
__FILE__
展開為當前檔案的名稱,作為字串字面量,可以透過 #line 指令更改
(宏常量)
__LINE__
展開為原始檔行號,一個整數常量,可以透過 #line 指令更改
(宏常量)
__DATE__
展開為翻譯日期,一個“Mmm dd yyyy”形式的字串字面量。月份名稱如同由 asctime 生成,如果月份日期小於 10,則“dd”的第一個字元為空格
(宏常量)
__TIME__
展開為翻譯時間,一個“hh:mm:ss”形式的字串字面量,如同由 asctime() 生成的時間
(宏常量)
__STDC_UTF_16__
(C23)
展開為 1 以指示 char16_t 使用 UTF-16 編碼
(宏常量)
__STDC_UTF_32__
(C23)
展開為 1 以指示 char32_t 使用 UTF-32 編碼
(宏常量)
__STDC_EMBED_NOT_FOUND____STDC_EMBED_FOUND____STDC_EMBED_EMPTY__
(C23)
分別展開為 012
(宏常量)

實現可以預定義以下附加宏名稱

__STDC_ISO_10646__
(C99)
如果 wchar_t 使用 Unicode,則展開為 yyyymmL 形式的整數常量;日期表示支援的最新 Unicode 修訂版
(宏常量)
__STDC_IEC_559__
(C99)
如果支援 IEC 60559,則展開為 1 (已棄用)(C23 起)
(宏常量)
__STDC_IEC_559_COMPLEX__
(C99)
如果支援 IEC 60559 複數算術,則展開為 1 (已棄用)(C23 起)
(宏常量)
__STDC_UTF_16__
(C11)
如果 char16_t 使用 UTF-16 編碼,則展開為 1
(宏常量)
__STDC_UTF_32__
(C11)
如果 char32_t 使用 UTF-32 編碼,則展開為 1
(宏常量)
__STDC_MB_MIGHT_NEQ_WC__
(C99)
如果基本字元集的成員(例如在基於 EBCDIC 的系統上使用 Unicode 作為 wchar_t)的 'x' == L'x' 可能為 false,則展開為 1
(宏常量)
__STDC_ANALYZABLE__
(C11)
如果支援可分析性,則展開為 1
(宏常量)
__STDC_LIB_EXT1__
(C11)
如果支援邊界檢查介面,則展開為整數常量 201112L
(宏常量)
__STDC_NO_ATOMICS__
(C11)
如果不支援原子型別和原子操作庫,則展開為 1
(宏常量)
__STDC_NO_COMPLEX__
(C11)
如果不支援複數型別複數數學庫,則展開為 1
(宏常量)
__STDC_NO_THREADS__
(C11)
如果不支援多執行緒,則展開為 1
(宏常量)
__STDC_NO_VLA__
(C11)
如果不支援可變長度陣列 和可變修改型別(直至 C23)具有自動儲存持續時間的(C23 起),則展開為 1
(宏常量)
__STDC_IEC_60559_BFP__
(C23)
如果支援 IEC 60559 二進位制浮點算術,則展開為 202311L
(宏常量)
__STDC_IEC_60559_DFP__
(C23)
如果支援 IEC 60559 十進位制浮點算術,則展開為 202311L
(宏常量)
__STDC_IEC_60559_COMPLEX__
(C23)
如果支援 IEC 60559 複數算術,則展開為 202311L
(宏常量)
__STDC_IEC_60559_TYPES__
(C23)
如果支援 IEC 60559 交換和擴充套件型別,則展開為 202311L
(宏常量)

這些宏的值(除了 __FILE____LINE__)在整個翻譯單元中保持不變。嘗試重新定義或取消定義這些宏會導致未定義行為。

預定義變數 __func__(有關詳細資訊,請參閱函式定義)不是預處理器宏,儘管它有時與 __FILE____LINE__ 一起使用,例如由 assert

(C99 起)

[編輯] 示例

#include <stdio.h>
 
// make function factory and use it
#define FUNCTION(name, a) int fun_##name(int x) { return (a) * x; }
 
FUNCTION(quadruple, 4)
FUNCTION(double, 2)
 
#undef FUNCTION
#define FUNCTION 34
#define OUTPUT(a) puts( #a )
 
int main(void)
{
    printf("quadruple(13): %d\n", fun_quadruple(13) );
    printf("double(21): %d\n", fun_double(21) );
    printf("%d\n", FUNCTION);
    OUTPUT(billion);               // note the lack of quotes
}

輸出

quadruple(13): 52
double(21): 42
34
billion

[編輯] 缺陷報告

以下行為改變的缺陷報告被追溯地應用於以前釋出的 C 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
DR 321 C99 不清楚 L'x' == 'x' 是否始終成立
在基本字元集中
為此添加了 __STDC_MB_MIGHT_NEQ_WC__

[編輯] 參考文獻

  • C23 標準 (ISO/IEC 9899:2024)
  • 6.10.4 宏替換 (p: 187-184)
  • 6.10.9 預定義宏名稱 (p: 186-188)
  • C17 標準 (ISO/IEC 9899:2018)
  • 6.10.3 宏替換 (p: 121-126)
  • 6.10.8 預定義宏名稱 (p: 127-129)
  • C11 標準 (ISO/IEC 9899:2011)
  • 6.10.3 宏替換 (p: 166-173)
  • 6.10.8 預定義宏名稱 (p: 175-176)
  • C99 標準 (ISO/IEC 9899:1999)
  • 6.10.3 宏替換 (p: 151-158)
  • 6.10.8 預定義宏名稱 (p: 160-161)
  • C89/C90 標準 (ISO/IEC 9899:1990)
  • 3.8.3 宏替換
  • 3.8.8 預定義宏名稱

[編輯] 另請參閱

C++ 文件,關於 替換文字宏