Namespaces
Variants

Replacing text macros

From cppreference.net

预处理器支持文本宏替换和函数式文本宏替换。

目录

语法

#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), 替换列表 可包含词法序列 __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)
分别展开为 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__ (详见 函数定义 )并非预处理器宏,尽管它常与 __FILE__ __LINE__ 配合使用,例如被 assert 使用。

(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++ 文档 关于 替换文本宏