替換文字宏
預處理器支援文字宏替換和函式式文字宏替換。
目錄 |
[編輯] 語法
#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),替換列表 可能包含標記序列 #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 });),則逗號被解釋為宏引數分隔符,導致由於引數數量不匹配而編譯失敗。
[編輯] # 和 ## 運算子
在類函式宏中,替換列表 中識別符號前的 #
運算子會對識別符號進行引數替換,並將結果用引號括起來,有效地建立一個字串字面量。此外,預處理器會新增反斜槓以轉義嵌入式字串字面量(如果有)周圍的引號,並根據需要將字串中的反斜槓加倍。所有前導和尾隨空格都將被刪除,並且文字中間的任何空格序列(但不包括嵌入式字串字面量內部)都會摺疊成一個空格。此操作稱為“字串化”。如果字串化的結果不是有效的字串字面量,則行為未定義。
當 #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 標準的每個版本而增加
|
__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) |
分別展開為 0、1 和 2 (宏常量) |
實現可以預定義以下附加宏名稱
__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__(有關詳細資訊,請參閱函式定義)不是預處理器宏,儘管它有時與 |
(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++ 文件,關於 替換文字宏
|