当前位置: C语言 -- 基础 -- 预处理指令

预处理指令(七)

1、VA_OPT替换

标识符__VA_OPT__只能出现在形参使用...的函数式宏的替换列表中。VA_OPT替换具有以下语法格式:

__VA_OPT__(pp-tokensopt)

其中__VA_OPT__ISO/IEC 9899:2024标准新增说明符;pp-tokens是可选的预处理标记。


VA_OPT替换的展开条件:当且仅当与函数式宏定义形参列表中...对应的实参列表存在,并且具有非空替换值时,__VA_OPT__(pp-tokensopt)中可选的预处理标记才会展开。

#define PRINT(format, ...) printf(format __VA_OPT__(,) __VA_ARGS__)

//不存在与...对应实参的情况。
//这种情况下__VA_OPT__(,)中的,符号不会展开。
PRINT("Hello World!\n");

//存在与...对应实参的情况。
//这种情况下__VA_OPT__(,)中的,符号会展开。
PRINT("%s%s\n", "Cease to struggle and you cease to live.", " --Thomas Carlyle");

ISO/IEC 9899:2024标准对VA_OPT替换作了一些限制:

-- 标识符__VA_OPT__应作为VA_OPT替换预处理标记序列的一部分出现;其闭合)符号通过跳过VA_OPT替换pp-tokens中成对出现的匹配( )来确定。

//VA_OPT替换的闭合)符号是倒数第二个)符号,即红色)符号。 
//倒数第一个)符号是宏替换列表的一部分。
#define FUNC(X, Y, ...) __VA_OPT__(F X Y) )

-- VA_OPT替换pp-tokens中不能包含标识符__VA_OPT__,即VA_OPT替换不能嵌套使用。

#define WRONG(...) __VA_OPT__(x __VA_OPT__(y) z) 0	//非法。

-- VA_OPT替换的pp-tokens应构成当前函数式宏的有效替换列表。

#define PRINT(format, ...) printf(format __VA_OPT__(*) __VA_ARGS__)	//非法。

2、实参替换

实参替换(argument substitution)是宏展开的一个过程,在这个过程中宏定义形参对应的标识符以及标识符__VA_ARGS____VA_OPT__会替换为宏调用实参序列中的标记序列,同时还可能包含VA_OPT替换中的标记序列。

#define SUM(x, y) ((x) + (y))
#define PRINT(format, ...) printf(format, ##__VA_ARGS__)

SUM(2, 5);
PRINT("China\n");
PRINT("%s%s", "Hello ", "World!\n");

确定函数式宏调用的实参后,开始实参替换。VA_OPT替换像形参一样处理。对于替换列表中的每个形参,如果该形参前既没有#预处理标记,也没有##预处理标记,并且该形参后也没有##预处理标记;该形参将按以下方式替换:

-- 如果形参具有VA_OPT替换格式,替换预处理标记是对应实参的预处理标记序列。

:前提是满足VA_OPT替换的展开条件。)

#define EMPTY
#define F1(...) func(0 __VA_OPT__(,) __VA_ARGS__)
#define F2(X, ...) func(0, X __VA_OPT__(,) __VA_ARGS__)

//满足VA_OPT替换的展开条件,替换预处理标记是符号,。
//F1(x,y,z)宏调用使用func(0, x, y, z)替换。
//F2(x,y,z)宏调用使用func(0, x, y, z)替换。
F1(x,y,z);
F2(x,y,z);

//不满足VA_OPT替换的展开条件,因为...无对应实参。
//F1()宏调用使用func(0)替换。
//F2(x)宏调用使用func(0, x)替换。
F1();
F2(x);

//不满足VA_OPT替换的展开条件,因为...对应实参为空。
//F1(EMPTY)宏调用使用func(0)替换。
//F2(x, EMPTY)宏调用使用func(0, x)替换。
F1(EMPTY);
F2(x, EMPTY);

-- 如果形参不具有VA_OPT替换格式,替换预处理标记是对应实参中包含的所有宏展开后的预处理标记。实参预处理标记在实参替换前完成实参中所有宏展开。

#define TWO 2
#define FIVE 5
#define SUM(x, y) ((x) + (y))

//SUM(TWO, FIVE)宏调用使用2 + 5替换。
SUM(TWO, FIVE);

标识符__VA_ARGS__只能出现在形参使用...的函数式宏的替换列表中,标识符__VA_ARGS__像形参一样处理,可变实参构成的预处理标记用于替换标识符__VA_ARGS__


VA_OPT替换对应实参的预处理标记序列定义如下:

-- 如果标识符__VA_ARGS__的替换既不是#运算符的操作数,也不是##运算符的操作数,并且不包含任何预处理标记,那么VA_OPT替换对应实参由单个占位标记预处理标记(placemarker preprocessing token)构成;


-- 否则,VA_OPT替换对应实参由其所包含的预处理标记展开结果构成;作为当前函数式宏的替换列表,此过程发生在移除占位标记、重新扫描和进一步替换之前。

:占位标记不会出现在语法中,因为占位标记是仅存在于编译阶段步骤4的临时实体。占位标记在字符串化之前移除,并且可以通过重新扫描和进一步替换移除。)

typedef struct {
  int i;
  double d;
} S;

typedef int Array[3];

#define STRUCT_S(name, ...) S name __VA_OPT__(= {__VA_ARGS__})
#define ARRAY_3(name, ...) Array name __VA_OPT__(= {__VA_ARGS__})

//等价于S s1;。
STRUCT_S(s1);
//等价于S s2 = {5, 3.14};。
STRUCT_S(s2, 5, 3.14);

//等价于Array arr1;。
ARRAY_3(arr1);
//等价于Array arr2 = {2, 4, 6};。
ARRAY_3(arr2, 2, 4, 6);