替换文本宏
预处理器支持文本宏替换和函数式文本宏替换。
目录 |
[编辑] 语法
#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++ 文档,关于 替换文本宏
|