预处理指令(七)
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);