C++宏展开

news2024/9/29 22:16:25

感觉自己一直对C++的宏展开没有细致地研究过,这两天深入地学习了一下,做个笔记。

文章目录

  • 宏展开基本规则
  • 宏嵌套展开
  • 补充说明
  • 参考资料


首先明确宏展开,是在预处理阶段进行的,进入编译期就是宏展开之后的代码了,所以不会有什么编译期的类型检查。

宏展开基本规则

基础语法

#define identifier replacement-list(optional)
#define identifier(parameters) replacement-list(optional)
#define identifier(parameters, ...) replacement-list(optional) (since C++11)
#define identifier(...) replacement-list(optional) (since C++11)
#undef identifier

前两个比较好理解,第一个宏展开类型位 object-like,后面的宏展开类型是 function-like,下面主要说明下可变参数的情况。
在宏定义中,替换列表中可以使用 __VA_ARGS_identifier 标识符访问可变参数。
举几个例子就清晰了

#define F(...) f(__VA_ARGS__)
#define G(X, ...) f(0, X, ##__VA_ARGS__)

F(1, 2, 3); // f(1, 2, 3);
G(1); // f(0, 1);

这里 G(X, ...)f(0, X, ##__VA_ARGS__) 中的 ##__VA_ARGS__ 是用来解决当 X 后面没有其它参数了,去除掉多余的 ,,注意在 C++20 中引入 __VA_OPT__宏来更规范地代替 ##__VA_ARGS__,但我在MSVC里测试还不支持,查了资料 Clang 和 GCC 都是支持的。

此外还有两个常用的运算符 #### 使传入的实参变成字符串,## 使得传入的两个实参连接(concatenate),具体还是看两个例子

#define CHAR_(c) #c
#define CONBINE_(a, b) a##b

CHAR_(1223) // "1223"
CONBINE_(12, 23) // 1223
CHAR_(CONBINE_(12, 23)) // "CONBINE_(12, 23)"

最后一行涉及到宏嵌套,放到下一节细说。


宏嵌套展开

宏嵌套就是宏的形参可以是一个宏,形成一个层层嵌套的关系,宏嵌套展开的流程图如下:

这里的两个条件分别是:

  • 条件1:当前宏体中是否含有 ### 对应上一节最后提到的情况
  • 条件2:当前是否是最内层

这里涉及到宏定义不能递归展开,所以实际要通过两个标记位实现:

  • replacing bit:这个标记位标识宏当前是否正在被它的替换列表替代,下面用rb简称
  • unavailable bit:这个标记位表示token是否还能被宏展开,下面用ub简称

比如传入一个token T,其具体的宏展开流程如下:

  1. 初始token T的rb和ub都是清0的
  2. 判断T是否能宏展开:
    2.1 如果 T 是宏,且其rb为1,cpp设置 T 的ub为1
    2.2 如果 T 是object-like宏 且 ub 为0,T可以宏展开
    2.3 如果 T 是function-like宏 且 ub 为0,且 T后面有 (,T可以宏展开
  3. 如果 T 不能宏展开,就添加到当前输出token列表。如果能宏展开,进行两阶段的宏展开:
    3.1 T 是一个function-like宏,cpp 扫描提供给 T 的所有实参,如果实参也是宏,要对实参尝试进行宏展开。实参宏展开流程和T宏展开流程一致,都是先判断再展开,只是 T 展开完的结果是放入主预处理输出,而实参的输出则是放入对T每个实参独有的替换token列表。也会记住实参展开前的原始宏,因为 T 中可能有用 ### 连接,需要这个原始宏,而不是宏展开之后的结果。
    3.2 T 如果有实参,cpp会使用T的替换列表,来用宏展开完全的实参token list替换T中的形参。也会执行 ### 的字符串化和粘贴。然后,它逻辑上将生成的token添加到输入列表的前面。最后,cpp将名为T的宏的rb设置为true。
  4. rescan
    T的rb现在为真,cpp继续处理添加到输入列表的tokens(是从这个位置开始继续检查宏展开,和前面的没关系)。这可能会产生更多的宏展开。一旦cpp已经使用了由替换列表生成的所有tokens,预处理器会清0rb

还是举几个例子

例1

#define FL(x) ((x)+1)     // function-like macro
FL(FL(5))    // => ((((5)+1))+1)

我们按照上面的流程分析一下,首先输入的 token T为 FL,其初始rb和ub都是清0的,
然后 FL(FL(5)) 明显是一个 function-like 的宏,且ub为0,且FL后面有(,那么可以宏展开。
那么首先对其实参尝试进行宏展开,FL(5)=>((5)+1),产生的token就是((5)+1),此时FL的rb设为1,然后再用实参展开的结果替换形参,即FL(((5)+1))=>(((5)+1)+1)

例2

#define ID(arg) arg
ID(ID)(ID)(X)   // => ID(ID)(X)

输入的 token T是 ID,明显是一个 function-like 宏,且ub为0,后面有(,可以宏展开。
对其实参先进行尝试宏展开,实参不能宏展开,那么替换列表里的token就是 ID,替换掉形参,即
ID(ID)=>ID 然后设置 ID 的rb为1,进行rescan,ID 的rb为1,其ub设为1,发现虽然 ID 还是一个function-like,但其ub被设为 1 了,所以不能再宏展开了,括号里面的由于没有 ( 跟随所以也不能宏展开。

例3

#define EMPTY
#define SCAN(x)     x
#define EXAMPLE_()  EXAMPLE
#define EXAMPLE(n)  EXAMPLE_ EMPTY()(n-1) (n)
EXAMPLE(5) // => EXAMPLE_()(5 - 1) (5)
SCAN(EXAMPLE(5)) // => EXAMPLE_()(5 - 1 - 1)(5 - 1)(5)

对于 EXAMPLE(5) 其是一个 function-like 的宏,实参也不能宏展开,那么直接替换 EXAMPLE_ EMPTY()(5-1) (5) 然后rescan,从前往后扫,遇到 EMPTY宏 替换,最后结果就为 EXAMPLE_()(5 - 1) (5)

而对于 SCAN(EXAMPLE(5)) 其也是一个function-like宏,实参可以宏展开,最终结果就是上面的 EXAMPLE_()(5 - 1)(5),然后实参替换掉形参,那么需要rescan,EXAMPLE_() 宏可以替换为 EXAMPLE,然后 EXAMPLE(5 - 1) 可以替换为 EXAMPLE_ EMPTY()(5 - 1 - 1)(5 - 1) 然后,rescan 发现 EMPTY 可以替换,然后最终结果就为 EXAMPLE_()(5 - 1 - 1)(5 - 1)(5)


补充说明

补充1:宏定义多行写可以用 \ 换行

#define FL(x) \
        ((x)+1) 

补充2:宏定义会对传入字符串进行适当地处理,下面假如反斜杠防止多个""

#define STR(s) #s

STR("123") // "\"123\""

参考资料

Recursive macros with C++20 VA_OPT
Replacing text macros
可视化 C/C++ 宏扩展
深入聊一聊C/C++中宏展开过程
聊一聊新的宏__VA_OPT__
C语言复杂宏展开的替换顺序及定义查看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2087740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Flask的新冠疫情信息可视化查询系统【案例模板】

文章目录 有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主每文一语 有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主 本项目是一个案例学习项目,可以作为新手进行学习系统的框架,本项目有数据库…

Git 学习

一、基本使用 1. 基本理论 Git 是一个免费的、开源的分布式版本控制系统,可以快速高效地处理从小型到大型的项目;版本控制是一种记录一个或者若干个文件内容变化,以便来查阅特定版本修订情况的系统 集中化版本控制系统:SVN, CV…

单片机使用cJSON的坑

文章目录 问题解决办法方法一方法二 问题 单片机USAR串口通信我想用json, 我不想用 分隔符的方式。感觉性能够,还有就是方便理解。 mcu型号 : AT32F415系列 雅特力的。 cJSON库: https://github.com/DaveGamble/cJSON/tree/master 只要把 cJSON.h 和 c…

云计算实训39——Harbor仓库的使用、Docker-compose的编排、YAML文件

一、Harbor部署 1.验证python版本 [rootdocker2 ~]#python --version 2.安装pip [rootdocker2 ~]# yum -y install python2-pip #由于版本过低,需要对其进行一个升级 #更新pip [rootdocker2 ~]#pip install --upgrade pip 3.指定版本号 [rootdocker2 ~]# p…

geodatatool(地图资源工具)下载高德数据及数据共享

利用geodatatool(地图资源工具)3.8(新)下载高德POI数据: 选择类型如下: 数据效果如下,由于用的免费的key,所以可能数据下载还不完全,但已经很多了: 下载数据…

小模型大智慧!港大重磅开源EasyRec,推荐系统进入语言模型时代

在当今的信息时代,我们每天都被海量信息所包围,不断面临各种选择。从网上购物、音乐播放到视频推荐,推荐系统已经成为我们生活中不可或缺的一部分。那么,这些系统是如何运作的?它们又是如何在信息的洪流中帮助我们找到…

《使用 LangChain 进行大模型应用开发》学习笔记(一)

前言 本文是 Harrison Chase (LangChain 创建者)和吴恩达(Andrew Ng)的视频课程《LangChain for LLM Application Development》(使用 LangChain 进行大模型应用开发)的学习笔记。由于原课程为全英文视频课…

智能优化特征选择|基于鲸鱼WOA优化算法实现的特征选择研究Matlab程序(KNN分类器)

智能优化特征选择|基于鲸鱼WOA优化算法实现的特征选择研究Matlab程序(KNN分类器) 文章目录 一、基本原理原理流程举个例子总结 二、实验结果三、核心代码四、代码获取五、总结 智能优化特征选择|基于鲸鱼WOA优化算法实现的特征选择研究Matlab程序&#x…

Android 优化之 查找so 文件的来源

序言 有时候我们需要优化apk的包体积大小。比如下面这样的。一个so文件大小有10M。但是我们并不知道so文件是那个库引入的。所以需要研究一下。 方法 在参考网上现有方法,加上自己测试以后。有了下面的成功。而且在gradle 8.4.2都可以成功。相信大家都可以成功。…

MathType常见问题汇总

文章目录 MathType常见问题汇总一、如何将MathType内嵌到WPS工具栏中?二、在word中,如何批量修改所有MathType公式的字体以及大小格式?三、如何解决插入MathType公式后的行间距发生改变?参考 MathType常见问题汇总 一、如何将Mat…

CEASC:基于全局上下文增强的自适应稀疏卷积网络在无人机图像上的快速目标检测

Adaptive Sparse Convolutional Networks with Global Context Enhancement for Faster Object Detection on Drone Images 摘要 提出了一种基于稀疏卷积的探测头优化方法,该方法在精度和效率之间取得了较好的平衡。然而,该算法对微小物体的上下文信息融…

C/C++ JSON ORM

structs #include "json_struct.h" #include <vector>JS_ENUM(Error, None, InvalidRange, IllegalParam, Nullptr, OverLimitLen) JS_ENUM_DECLARE_STRING_PARSER(Error)// 搜索匹配区域 struct RangeContent {size_t start;size_t end;std::strin…

基于协同过滤与情感分析的酒店评论分析与景区推荐系统实现

文章目录 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主项目介绍系统界面推荐模块主题分类文本可视化每文一语 有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 项目介绍 近年来&#xff0c;旅游行业风生水起&#…

#单片机高级 硬件部分笔记

课程内容 硬件基础知识PCB基础知识嘉立创EDA&#xff08;专业版&#xff09;软件的安装及使用PCB设计PCB设计规则&#xff08;原理图、布局、布线&#xff09;项目&#xff08;暂定&#xff09; 1、硬件基础 初级硬件工程师 熟练掌握数字电路、模拟电路知识&#xff0c;熟悉常用…

unity的问题记录(信息管理)

闭包 捕获引用&#xff1a;当你在委托或 lambda 表达式中使用外部变量时&#xff0c;它们捕获的是这个变量的引用&#xff0c;而不是当时的值。变量的生命周期&#xff1a;捕获的变量的生命周期不受限于它的作用域&#xff0c;委托可以在变量的作用域结束后继续访问它。 为了…

今晚8点直播预告——模拟RCT,真实世界研究新方法,快来了解一下吧

这是讲座预告&#xff0c;我们来向大家介绍一下真实世界研究的新方法—模拟RCT&#xff01; 郑老师喜欢交流&#xff0c;于是在2024年&#xff0c;决定邀请各位一起参加统计学沙龙&#xff0c;基本每周一期&#xff0c;欢迎各位朋友来交流、讲课。 本期沙龙&#xff0c;在8月29…

【ubuntu笔记】拉取docker镜像

拉取docker镜像 更换国内源 修改配置文件 sudo vim /etc/docker/daemon.json{"registry-mirrors": ["https://ustc-edu-cn.mirror.aliyuncs.com/","https://hub-mirror.c.163.com","https://mirror.baidubce.com","https://cc…

【Java】Maven多环境切换实战(实操图解)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 Maven多环境配置学习4.2 切换环境4.2.1 先打包4.2.2 之后可以切换 五、总结…

聊聊Netty异常传播链与最佳实践

写在文章开头 Netty通过责任链的思想解耦了各个业务的处理逻辑,是的用户可以非常方便的根据不同的生命周期进行相应的业务处理。而本文将针对Netty中的异常和异常传播过程进行分析,并给出最佳的处理技巧,希望对你有帮助。 Hi,我是 sharkChili ,是个不断在硬核技术上作死的…

Electron 项目实战 03: 实现一个截图功能

实现效果 实现思路 创建两个window&#xff0c;一个叫mainWindow&#xff0c;一个叫cutWindowmainWindow&#xff1a;主界面用来展示截图结果cutWindow&#xff1a;截图窗口&#xff0c;加载截图页面和截图交互逻辑mainWindow 页面点击截图&#xff0c;让cutWIndow 来实现具体…