这篇文章的目的是介绍VC宏的特性分析
总结了vc宏的常用用法,和在分析开源项目时学到的一些宏使用的经验心得和大家分享.
微软msvc编译器在默认状态下不展开宏参数,需要解决这个问题需要开启/Zc:preprocessor编译选项
edbK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6N6r3q4U0K9$3!0$3k6i4u0X3L8r3!0%4i4K6u0W2j5$3!0E0i4K6u0r3M7i4g2W2M7%4c8A6L8$3&6K6i4K6u0r3y4e0p5K6y4o6f1J5x3#2)9J5c8X3#2K6N6X3y4Q4x3X3c8V1L8$3g2K6L8Y4c8Q4x3X3c8W2P5s2m8S2L8X3c8Q4x3X3c8$3j5g2)9J5k6r3q4J5k6%4y4Q4x3X3c8U0L8%4u0J5k6h3y4@1L8s2V1`.
宏的递归调用解决方案源自以下文章
3f0K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6K6N6r3q4U0K9$3!0$3k6i4u0X3L8r3!0%4i4K6u0W2j5$3!0E0i4K6u0r3M7i4g2W2M7%4c8A6L8$3&6K6i4K6u0r3x3e0t1@1y4o6M7#2y4e0N6Q4x3V1k6U0j5h3&6Q4x3X3c8%4k6g2)9J5k6r3S2S2N6X3g2Q4x3X3c8J5k6h3y4#2M7Y4y4A6N6X3g2Q4x3X3c8E0j5h3y4J5L8%4x3`.
涉及开源项目
c5aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4L8$3!0Y4L8r3g2Q4x3V1k6D9K9h3u0F1L8%4m8Q4x3V1k6T1L8r3!0T1i4K6u0r3L8h3q4K6N6r3g2J5i4K6u0r3K9h3&6U0L8s2g2V1k6g2)9J5c8X3&6G2M7q4)9J5c8X3u0S2M7$3g2Q4x3V1k6E0j5h3y4J5L8%4y4Q4x3X3g2Z5
vc宏的宏是不会处理数字运算的,实际的效果只是展开参数生成代码,上面的这段宏的代码使用的是递归调用pr自身展开参数但实际上对于调用pr(5)展开的结果如下:
实际上第一层嵌套pr(5-1))并没有被展开,由于是递归调用的关系,编译器默认只展开一层,展开的还是字面量pr(5-1)),实际上这个东西被编译器处理为未定义的宏变量,直接导致编译失败.那对于第一层宏嵌套有没有直接展开的方法,答案如下:
第一层嵌套可以用如上方式展开,pr_id ()由于的直接字面量宏函数,直接展开后变成pr,pr(5 -1))就被第二层展开了,这种延迟调用的方法仅对调用自身的宏函数有用,有几层EMPTY()就支持几层展开,其中要注意的一点是,下一次递归调用的入口需要定义另外一个宏实现展开后转义为自身,这样就消除了宏函数不能调用自身的编译器缺陷.
下面我们讨论另一种宏特性:判断可变长度宏参数列表是否是空参数:
宏有一个特殊下特性就是可以检测是否有括号(parenthesis),对于_NOP_IS_PAREN_PROBE宏的定义来说带括号从参数_NOP_IS_PAREN((1))转义成__VA_ARGS__=(1),NOP_IS_PAREN_PROBE VA_ARGS__就变成了_NOP_IS_PAREN_PROBE(1),是正确的带有括号的宏函数_NOP_IS_PAREN_PROBE(1)形式最终会被解释为_NOP_PROBE(),也就是 带有2个参数, 1参数列表形式,返回1,而对于_NOP_IS_PAREN(1)转义成__VA_ARGS=1,_NOP_IS_PAREN_PROBE __VA_ARGS__就变成了_NOP_IS_PAREN_PROBE1,这个_NOP_IS_PAREN_PROBE1最终会变成一个为未定义的宏变量,
NOP_IS_PROBE(...)=NOP_SECOND_ARG(VA_ARGS,0)实际上解析出来的结果是_NOP_SECOND_ARG(NOP_IS_PAREN_PROBE1,0)返回值是0,而对于_NOP_IS_PAREN((1)),被解释为_NOP_PROBE(),也就是 带有2个参数, 1参数列表形式,第二个参数是1,对于_NOP_IS_PAREN(...) 参数是一个以上的情况,结果也一样只不过第二个及它之后的参数跟在_NOP_IS_PROBE()=, 1,后面继续变成后续参数_VA_ARGS,但是这样不影响_NOP_IS_PROBE判断,因为已经解析成前2个参数为, 1不影响判断,这样就是实现了判断宏参数是否带有括号的解析方法,那如果_VA_ARGS__第一个参数为1 ,就变成了_NOP_IS_PAREN_PROBE1,而第二个参数也为1,这样到了_NOP_IS_PROBE解析出来的结果还是第二个参数1,就存在bug理论上也成立,这个问题留给有兴趣的读者自己解决,目前有的缓解方案是只传一个参数到_NOP_IS_PROBE,接下来介绍一种判断可变长度宏参数列表是否为空参数的特性:
这个特性采用_NOP_HAS_ARGS实现,同样用到了_NOP_IS_PAREN功能,当不存在宏参数时_NOP_FIRST_ARG(NOP_END_OF_ARGUMENTS VA_ARGS)解析成了_NOP_FIRST_ARG(_NOP_END_OF_ARGUMENTS),参数传给_NOP_FIRST_ARG返回自身,第二步变成了_NOP_END_OF_ARGUMENTS=_NOP_IS_PAREN,NOP_IS_PAREN加上这个之后的括号变成了_NOP_IS_PAREN()空参数模式,结果用之前讨论到的括号特性返回0,最后 到了_NOP_BOOL(0),NOP_BOOL又被转义成_NOP_NOT(NOP_NOT(0)),NOP_NOT实现是采用宏的拼接特性,拼接成_NOP_NOT_x,传给_NOP_IS_PROBE,结果是0=1,1=0,所以得到的最后结果是0,也就是说实现了当不存在宏参数时_NOP_HAS_ARGS返回0,当存在宏参数时_NOP_END_OF_ARGUMENTS 和__VA_ARGS__是个未定义的宏变量,即使右边带了一个(),得到的结果_NOP_END_OF_ARGUMENTS___VA_ARGS()这个东西也是是个未定义的宏变量,括号不参与转义,同样这个东西传给_NOP_BOOL得到的是_NOP_IS_PROBE(NOP_END_OF_ARGUMENTS___VA_ARGS())返回的结果是0,再not一次就是1,至此这个特性实现了判断可变长度宏参数列表是否为空的功能.
下面展示一段自己写的demo例子
这段demo采用vs2019编译器开启/Zc:preprocessor编译选项,c++版本最新模式,先要描述的测试功能是调用_NOP_MAP_TEST传入多个int参数每个参数转义成_(_NOP_FIRST_ARG(VA_ARGS) > 0) ? 1 :拼接,举例来说
( (1>0)?1:(_NOP_MAP_TEST(1,2) 0));展开后变成( (1>0)?1:((1>0)?!:(2>0):0,同样采用延迟调用和别名的方法调用自身_NOP_MAP_TEST,经过测试这种方式需要_NOP_EXPAND展开才编译通过,每展开一次递归执行一次,直到递归循环全部结束,
经过测试带_NOP_EXPAND编译通过结果正确,不带的模式无法通过编译,以上是笔者总结的一些关于vc宏研究的经验心得,与大家分享.
作者来自ZheJiang Guoli Security Technology,邮箱cbwang505@hotmail.com
((
5
=
=
1
)?
1
: pr(
5
-
1
))
pr(
5
)
/
/
展开后结果 ((
5
=
=
1
)?
1
: pr_id ()(
5
-
1
))
/
/
展开后
pr_id ()(
5
-
1
))
=
pr(
5
-
1
))
pr(
5
)
/
/
展开后结果 ((
5
=
=
1
)?
1
: pr_id ()(
5
-
1
))
/
/
展开后
pr_id ()(
5
-
1
))
=
pr(
5
-
1
))
/
/
Returns the second argument of a
list
.
_NOP_IF_ELSE(_NOP_IS_PAREN(__VA_ARGS__)) \
(_NOP_STRIP_PARENS __VA_ARGS__) \
(__VA_ARGS__)
/
/
__VA_ARGS__
=
(
1
)
int
valtrue
=
_NOP_IS_PAREN((
1
));
/
/
__VA_ARGS__
=
1
int
valfalse
=
_NOP_IS_PAREN(
1
);
/
/
Returns the second argument of a
list
.
_NOP_IF_ELSE(_NOP_IS_PAREN(__VA_ARGS__)) \
(_NOP_STRIP_PARENS __VA_ARGS__) \
(__VA_ARGS__)
/
/
__VA_ARGS__
=
(
1
)
int
valtrue
=
_NOP_IS_PAREN((
1
));
/
/
__VA_ARGS__
=
1
int
valfalse
=
_NOP_IS_PAREN(
1
);
/
/
Returns the first argument of a
list
.
_NOP_BOOL(_NOP_FIRST_ARG(_NOP_END_OF_ARGUMENTS_ __VA_ARGS__)())
/
/
Returns the first argument of a
list
.
[招生]系统0day安全-IOT设备漏洞挖掘(第6期)!