命名空间
变体
操作

替换文本宏

来自 cppreference.com

预处理器支持文本宏替换和类似函数的文本宏替换。

内容

[编辑] 语法

#define 标识符 替换列表 (可选) (1)
#define 标识符 ( 参数 ) 替换列表 (2)
#define 标识符 ( 参数, ... ) 替换列表 (3) (自 C99)
#define 标识符 ( ... ) 替换列表 (4) (自 C99)
#undef 标识符 (5)

[编辑] 解释

[编辑] #define 指令

#define 指令将 标识符 定义为宏,即它们指示编译器将所有后续出现的 标识符 替换为 替换列表,该列表可以可选地进行额外处理。如果标识符已经定义为任何类型的宏,则程序格式错误,除非定义相同。

[编辑] 类似对象的宏

类似对象的宏将 替换列表 替换每个定义的 标识符 的出现。#define 指令的版本 (1) 的行为完全一样。

[编辑] 类似函数的宏

类似函数的宏将 替换列表 替换每个定义的 标识符 的出现,此外还接受多个参数,然后将这些参数替换 替换列表 中的任何 参数 的相应出现。

类似函数的宏调用的语法类似于函数调用的语法:宏名称的每个实例后跟一个 ( 作为下一个预处理标记,引入了由替换列表替换的标记序列。该序列以匹配的 ) 标记结束,跳过中间匹配的左括号和右括号对。

参数的数量必须与宏定义 (参数) 中的参数数量相同,否则程序格式错误。如果标识符不是函数表示法,即自身后面没有括号,则不会进行任何替换。

#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)
扩展到形式为 yyyymmL 的整数常量,如果 wchar_t 使用 Unicode;日期表示支持的最新 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)
如果 'x' == L'x' 对基本字符集的成员可能为假,例如在使用 Unicode 表示 wchar_t 的基于 EBCDIC 的系统上,则扩展到 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)

[edit] 示例

#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

[edit] 缺陷报告

以下行为更改缺陷报告已追溯应用于先前发布的 C 标准。

DR 应用于 已发布的行为 正确行为
DR 321 C99 不清楚 L'x' == 'x' 是否始终成立
在基本字符集中
添加 __STDC_MB_MIGHT_NEQ_WC__ 用于此目的

[edit] 参考资料

  • C23 标准(ISO/IEC 9899:2024)
  • 6.10.4 宏替换(第 187-184 页)
  • 6.10.9 预定义宏名称(第 186-188 页)
  • C17 标准(ISO/IEC 9899:2018)
  • 6.10.3 宏替换(第 121-126 页)
  • 6.10.8 预定义宏名称(第 127-129 页)
  • C11 标准(ISO/IEC 9899:2011)
  • 6.10.3 宏替换(第 166-173 页)
  • 6.10.8 预定义宏名称(第 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 预定义宏名称

[edit] 另请参阅

C++ 文档 用于 替换文本宏