让人脑洞大开的vc宏高级用法

2023-12-08 0 5,125

简介

总结了vc宏的常用用法,和在分析开源项目时学到的一些宏使用的经验心得和大家分享.

分析

微软msvc编译器在默认状态下不展开宏参数,需要解决这个问题需要开启/Zc:preprocessor编译选项
https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly
宏的递归调用 源自以下文章
https://stackoverflow.com/questions/12447557/can-we-have-recursive-macros
涉及开源项目
https://github.com/google/libnop/blob/master/include/nop/base/macros.h

1
# define pr(n) ((n==1)? 1 : pr(n-1))

vc宏的宏是不会处理数字运算的,实际的效果只是展开参数生成代码,上面的这段宏的代码使用的是递归调用pr自身展开参数但实际上对于调用pr(5)展开的结果如下:

1
((5==1)? 1 : pr(5-1))

实际上第一层嵌套pr(5-1))并没有被展开,由于是递归调用的关系,编译器默认只展开一层,展开的还是字面量pr(5-1)),实际上这个东西被编译器处理为未定义的宏变量,直接导致编译失败.那对于第一层宏嵌套有没有直接展开的方法,答案如下:

1
2
3
4
5
6
7
8
9
10
# define EMPTY(...)
# define DEFER(...) __VA_ARGS__ EMPTY()
# define OBSTRUCT(...) __VA_ARGS__ DEFER(EMPTY)()
# define EXPAND(...) __VA_ARGS__
# define pr_id() pr
# define pr(n) ((n==1)? 1 : DEFER(pr_id)()(n-1))\
pr(5//展开后结果 ((5==1)? 1 : pr_id ()(5 -1))
//展开后
 pr_id ()(5 -1))=pr(5 -1))

第一层嵌套可以用如上方式展开,pr_id ()由于的直接字面量宏函数,直接展开后变成pr,pr(5 -1))就被第二层展开了,这种延迟调用的方法仅对调用自身的宏函数有用,有几层EMPTY()就支持几层展开,其中要注意的一点是,下一次递归调用的入口需要定义另外一个宏实现展开后转义为自身,这样就消除了宏函数不能调用自身的编译器缺陷.
下面我们讨论另一种宏特性:判断可变长度宏参数列表是否是空参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Returns the second argument of a list.
#define _NOP_SECOND_ARG(_, second, ...) second
#define _NOP_IS_PROBE(...) _NOP_SECOND_ARG(__VA_ARGS__,0)
#define _NOP_PROBE() _, 1
#define _NOP_REMOVE_PARENS(...)            \
  _NOP_IF_ELSE(_NOP_IS_PAREN(__VA_ARGS__)) \
    (_NOP_STRIP_PARENS __VA_ARGS__)        \
    (__VA_ARGS__)
#define _NOP_IS_PAREN(...) _NOP_IS_PROBE(_NOP_IS_PAREN_PROBE __VA_ARGS__)
#define _NOP_IS_PAREN_PROBE(...) _NOP_PROBE()
//__VA_ARGS__=(1)
int valtrue=_NOP_IS_PAREN((1));
//__VA_ARGS__=1
int valfalse=_NOP_IS_PAREN(1);

宏有一个特殊下特性就是可以检测是否有括号(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,接下来介绍一种判断可变长度宏参数列表是否为空参数的特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Returns the first argument of a list.
#define _NOP_FIRST_ARG(first, ...) first
#define _NOP_REST_ARG(_, ...) __VA_ARGS__
#define _NOP_CAT(a, ...) a##__VA_ARGS__
#define _NOP_CAT_ARG(a, ...) a##__VA_ARGS__,0
#define _NOP_NOT(x) _NOP_IS_PROBE(_NOP_CAT_ARG(_NOP_NOT_, x))
#define _NOP_NOT_0 _NOP_PROBE()
#define _NOP_NOT_1 _, 0
#define _NOP_BOOL(x) _NOP_NOT(_NOP_NOT(x))
#define _NOP_IF_ELSE(condition) __NOP_IF_ELSE(_NOP_BOOL(condition))
#define __NOP_IF_ELSE(condition) _NOP_CAT(_NOP_IF_, condition)
#define _NOP_IF_1(...) __VA_ARGS__ _NOP_IF_1_ELSE
#define _NOP_IF_0(...) _NOP_IF_0_ELSE
#define _NOP_IF_1_ELSE(...)
#define _NOP_IF_0_ELSE(...) __VA_ARGS__
#define _NOP_HAS_ARGS(...) \
 _NOP_BOOL(_NOP_FIRST_ARG(_NOP_END_OF_ARGUMENTS_ __VA_ARGS__)())
#define _NOP_END_OF_ARGUMENTS_(...) _NOP_IS_PAREN(__VA_ARGS__)

这个特性采用_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例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define _NOP_EMPTY()
#define _NOP_DEFER1(m) m _NOP_EMPTY()
#define _NOP_DEFER2(m) m _NOP_EMPTY _NOP_EMPTY()()
#define _NOP_DEFER3(m) m _NOP_EMPTY _NOP_EMPTY _NOP_EMPTY()()()
 // Recursive expansion macros.
#define _NOP_EXPAND0(...) __VA_ARGS__
#define _NOP_EXPAND1(...) _NOP_EXPAND0(_NOP_EXPAND0(_NOP_EXPAND0(__VA_ARGS__)))
#define _NOP_EXPAND2(...) _NOP_EXPAND1(_NOP_EXPAND1(_NOP_EXPAND1(__VA_ARGS__)))
...
#define _NOP_EXPAND5(...) _NOP_EXPAND4(_NOP_EXPAND4(_NOP_EXPAND4(__VA_ARGS__)))
#define _NOP_EXPAND(...)  _NOP_EXPAND5(_NOP_EXPAND5(_NOP_EXPAND5(__VA_ARGS__)))
#define _NOP_MAP_TEST_FAKE(...)\
(_NOP_FIRST_ARG(__VA_ARGS__) > 0) ? 1 :
#define _NOP_MAP_TEST(...)                                    \
  _NOP_IF_ELSE(_NOP_HAS_ARGS(__VA_ARGS__)) (                     \
     _NOP_MAP_TEST_FAKE(__VA_ARGS__)     \
    _NOP_DEFER3(__NOP_MAP_TEST)()(_NOP_REST_ARG(__VA_ARGS__)) \
  )()
#define __NOP_MAP_TEST() _NOP_MAP_TEST
int main()
{
    //这种情况编译通过
    int =( (1>0)?1:(_NOP_EXPAND(_NOP_MAP_TEST(1,2,3,4))  0));
    //编译不通过
    //int =( (1>0)?1:(_NOP_MAP_TEST(1,2,3,4)  0));
}

这段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宏研究的经验心得,与大家分享.

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

注:在使用本系统时,使用方必须在国家相关法律法规范围内并经过国家相关部门的授权许可,禁止用于一切非法行为。使用用途仅限于测试、实验、研究为目的,禁止用于一切商业运营,本团队不承担使用者在使用过程中的任何违法行为负责。

83源码 技术教程 让人脑洞大开的vc宏高级用法 https://www.83ym.com/176.html

认准唯一TG:@ym830

常见问题
  • 站内所有资源,针对不同等级VIP会员可直接下载,特殊资源商品会注明是否免费,指会员所享有根据选择购买的会员选项所享有的特殊服务,具体以本站公布的服务内容为准。
查看详情
  • 按照我国的法律规定,运营网络棋牌首先需要成立一个注册正规备案的公司,然后申请网站备案、文网文、ICP等等,这些证件缺一不可。 一.注册公司 在当地工商进行注册,公司名称以“XX科技有限公司”为名,如:富裕棋牌经营范围填写“计算机软硬件、网络设备的设计开发与购销”。 二.域名及网站备案 在国内从事网站经营活动就必须经过相关部门的备案,因此棋牌运营商在购买了域名后,就要到当地网监局办理网站备案,或者请服务器提供商代为备案。 三.申请文网文 文网文全称为网络文化经营许可证,是从事经营性互联网文化活动所必需的资质。一般是需要到当地省一级(省、直辖市、自治区)的文化行政部门提出申请,并经由当地的文化行政部门合法批准。次资质要求申请公司注册资金必需达到1000万,并提供游戏版权证明文件。 四.申请ICP ICP又称为增值电信业务许可证,所有网络游戏运营商均需要办理ICP许可证,此证件要求公司注册资金1000万,需到当地市级通讯管理局办理。 五.申请文网游——游戏备案 根据《网络游戏管理暂行办法》(文化部第49号)的规定,国产网络游戏在上网运营之日起30日内应当按规定向国务院文化行政部门履行备案手续。 以上就是网络棋牌游戏正规运营所必需的资质证明。一般作为正规有实力的棋牌游戏开发公司,不光要具备所有的正规资质,而且会对投资者、代理商等合作伙伴给予相关指导和协助,与合作伙伴携手共赢!
查看详情

相关文章

猜你喜欢
官方客服团队

为您解决烦忧 - 24小时在线 专业服务