Replacing text macros
预处理器支持文本宏替换和函数式文本宏替换。
目录 |
语法
#define
标识符
替换列表
(可选)
|
(1) | ||||||||
#define
标识符
(
参数
)
替换列表
|
(2) | ||||||||
#define
标识符
(
参数
, ... )
替换列表
|
(3) | (C99起) | |||||||
#define
标识符
( ... )
替换列表
|
(4) | (C99起) | |||||||
#undef
标识符
|
(5) | ||||||||
说明
#define 指令
#define
指令将
identifier
定义为宏,即指示编译器将所有后续出现的
identifier
替换为
replacement-list
,该替换列表可选择性地进行额外处理。若该标识符已定义为任何类型的宏,则程序非良构,除非定义完全一致。
对象式宏
对象式宏会将所有出现的已定义
标识符
替换为
替换列表
。
#define
指令的版本(1)正是如此运作的。
函数式宏
类函数宏会将每个已定义的 标识符 出现处替换为 替换列表 ,同时接收若干参数,这些参数随后会替换 替换列表 中对应 形参 的所有出现位置。
函数式宏调用的语法与函数调用语法相似:每个宏名称实例后紧跟一个 ( 作为下一个预处理标记时,会引入被替换列表所替换的标记序列。该序列由匹配的 ) 标记终止,同时跳过中间成对匹配的左右括号。
参数数量必须与宏定义中的参数数量相同,否则程序格式错误。若标识符未采用函数表示法(即自身后未跟随圆括号),则完全不会被替换。
版本(2)的
#define
指令定义了一个简单的函数式宏。
版本 (3) 的
#define
指令定义了一个可变参数数量的函数式宏。额外的参数可通过
__VA_ARGS__
标识符访问,该标识符随后会被替换为与待替换标识符一同提供的参数。
版本 (4) 的
#define
指令定义了一个类似函数的宏,该宏具有可变数量的参数,但不包含常规参数。这些参数只能通过
__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)
(deprecated in C23)
|
若支持 IEC 60559 则展开为
1
(宏常量) |
|
__STDC_IEC_559_COMPLEX__
(C99)
(deprecated in C23)
|
若支持 IEC 60559 复数运算则展开为
1
(宏常量) |
|
__STDC_UTF_16__
(C11)
|
若
char16_t
使用 UTF-16 编码则展开为
1
(宏常量) |
|
__STDC_UTF_32__
(C11)
|
若
char32_t
使用 UTF-32 编码则展开为
1
(宏常量) |
|
__STDC_MB_MIGHT_NEQ_WC__
(C99)
|
若对于基础字符集的成员(例如在使用 Unicode 表示
wchar_t
的 EBCDIC 系统上)可能存在
'x'
==
L
'x'
为假的情况,则展开为
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)
|
若不支持
变长数组
和可变修改类型
(until C23)
的自动存储期
(since 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> // 创建函数工厂并使用 #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); // 注意没有引号 }
输出:
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 宏替换(页码:151-158)
-
- 6.10.8 预定义宏名(页码:160-161)
- C89/C90 标准 (ISO/IEC 9899:1990):
-
- 3.8.3 宏替换
-
- 3.8.8 预定义宏名称
参见
|
C++ 文档
关于
替换文本宏
|