当前位置: 首页 > news >正文

chrome-base源码分析(1)macros模块

Chrome-base源码分析(2)之Macros模块

Author:Once Day Date:2024年6月29日

漫漫长路,才刚刚开始…

全系列文章请查看专栏: 源码分析_Once-Day的博客-CSDN博客

参考文档:

  • macros - Chromium Code Search
  • Chrome base 库详解:工具类和常用类库_base::repeatingcallback-CSDN博客
  • For Developers (chromium.org)
  • 手动代码中包含C++20 __VA_OPT__错误的类函数宏-腾讯云开发者社区-腾讯云 (tencent.com)

文章目录

      • Chrome-base源码分析(2)之Macros模块
        • 1. 概述
        • 2. concat.h
        • 3. if.h
        • 4. is_empty.h
        • 5. remove_parens.h

1. 概述

chrome-base源码中macros模块是一个比较简单的模块,定义了一些简单的宏,有五个源文件,下面一一介绍。

2. concat.h

源码如下:

// A macro that expands to the concatenation of its arguments. If the arguments
// are themselves macros, they are first expanded (due to the indirection
// through a second macro). This can be used to construct tokens.
#define BASE_CONCAT(a, b) BASE_INTERNAL_CONCAT(a, b)// Implementation details: do not use directly.
#define BASE_INTERNAL_CONCAT(a, b) a##b

这个非常基础的关键字拼接宏,例如下面所示:

TEST(MacrosTest, Concat) {auto a = BASE_CONCAT(10, 20);std::cout << "a: " << a << std::endl;EXPECT_EQ(a, 1020);auto b = BASE_CONCAT(5000, 6000);std::cout << "b: " << b << std::endl;EXPECT_EQ(b, 50006000);
}
>>> a: 1020
>>> b: 50006000

BASE_CONCAT(10, 20)会输出1020,这是可以作为源码字面量的值,并不是"10" + "20" = "1020"这种字符串拼接。

3. if.h

源码如下:

// Given a `_Cond` that evaluates to exactly 0 or 1, this macro evaluates to
// either the `_Then` or `_Else` args. Unlike a real conditional expression,
// this does not support conditions other than `0` and `1`.
#define BASE_IF(_Cond, _Then, _Else) \BASE_CONCAT(BASE_INTERNAL_IF_, _Cond)(_Then, _Else)// Implementation details: do not use directly.
#define BASE_INTERNAL_IF_1(_Then, _Else) _Then
#define BASE_INTERNAL_IF_0(_Then, _Else) _Else

这段代码定义了一个名为BASE_IF的宏,用于实现编译期的条件选择功能。

宏接受三个参数:_Cond_Then_Else_Cond必须是一个计算结果为0或1的表达式。

根据_Cond的值,宏会将其展开为_Then_Else参数的内容。

宏的实现依赖于两个内部宏BASE_INTERNAL_IF_1BASE_INTERNAL_IF_0,它们分别选择_Then_Else参数。

通过巧妙的宏拼接,BASE_IF能够在编译期根据条件选择代码,而不会产生运行时开销。

例如下面所示:

TEST(MacrosTest, If) {auto a = BASE_IF(1, 10, 20);std::cout << "a: " << a << std::endl;EXPECT_EQ(a, 10);auto b = BASE_IF(0, 100, 200);std::cout << "b: " << b << std::endl;EXPECT_EQ(b, 200);
}
>>> a: 10
>>> b: 200
4. is_empty.h

源码如下:

// A macro that substitutes with 1 if called without arguments, otherwise 0.
#define BASE_IS_EMPTY(...) BASE_INTERNAL_IS_EMPTY_EXPANDED(__VA_ARGS__)
#define BASE_INTERNAL_IS_EMPTY_EXPANDED(...) \BASE_INTERNAL_IS_EMPTY_INNER(_, ##__VA_ARGS__)
#define BASE_INTERNAL_IS_EMPTY_INNER(...) \BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(__VA_ARGS__, 0, 1)
#define BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(e0, e1, is_empty, ...) is_empty

这段代码定义了一个名为BASE_IS_EMPTY的宏,用于检查宏是否被传递了参数。如果宏调用时没有传递任何参数,则展开为1,否则展开为0。

宏的实现依赖于几个内部宏:

  • BASE_INTERNAL_IS_EMPTY_EXPANDED: 对传入的参数进行展开,并在前面添加一个下划线。
  • BASE_INTERNAL_IS_EMPTY_INNER: 在展开后的参数列表前添加固定的参数。
  • BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED: 根据参数个数选择结果,如果只有固定参数则说明原宏调用时没传参数,返回1,否则返回0。

通过这种巧妙的宏展开和参数匹配,BASE_IS_EMPTY能够在编译期判断宏调用时是否传递了参数。下面是一些示例:

BASE_IS_EMPTY()      // 展开为 1
BASE_IS_EMPTY(a)     // 展开为 0 
BASE_IS_EMPTY(a, b)  // 展开为 0

这个宏常用于其他宏定义中,用于根据传参情况生成不同的代码,或者进行静态断言检查宏参数等。比如可以写一个字符串连接的宏:

#define CONCAT(a, ...) \BASE_IF(BASE_IS_EMPTY(__VA_ARGS__), a, CONCAT_INNER(a, __VA_ARGS__))// 当只传一个参数时直接返回,多个参数时递归拼接  

在windows上编译时,需要传入/Zc:preprocessor参数,以确保C++预编译器的行为和GCC一致。

windows上默认行为比较特殊,如下:

// windows行为
BASE_IS_EMPTY(a, b, c, d)
>>> BASE_INTERNAL_IS_EMPTY_EXPANDED(a, b, c, d)
>>> BASE_INTERNAL_IS_EMPTY_INNER(_, a, b, c, d)
>>> BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(_, a, b, c, d, 0, 1)
e0 =  _, a, b, c, d  //因为windows默认把__VA_ARGS__当成整体了,这与GCC行为存在差异
e1 =  0
e2 =  1

GCC的默认行为如下:

// GCC行为
BASE_IS_EMPTY(a, b, c, d)
>>> BASE_INTERNAL_IS_EMPTY_EXPANDED(a, b, c, d)
>>> BASE_INTERNAL_IS_EMPTY_INNER(_, a, b, c, d)
>>> BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(_, a, b, c, d, 0, 1)
e0 =  _
e1 =  a # GCC上, 默认是按照展开位置来抉择
e2 =  b
......

虽然GCC的行为是正常展开了变量,参数和位置能一一对应,但是依旧不满足逻辑,如下:

// BASE_IS_EMPTY() 应该返回 1
BASE_IS_EMPTY() => BASE_INTERNAL_IS_EMPTY_INNER(_) => BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(_, 0, 1)
>>> 所以is_empty == 1
// BASE_IS_EMPTY(a, b, c, d) 应该返回0
BASE_IS_EMPTY(a, b, c, d) => BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(_, a, b, c, d, 0, 1)
>>> 所以is_empty == b, 这并不符合预期

所以需要对命令稍加改造:

// 在Windows上使用msvc时, 需要/Zc:preprocessor参数, 否则宏处理会有问题
// reference: https://stackoverflow.com/questions/77700691/getting-va-opt-to-be-recognized-by-visual-studio
// 对于多个参数的情况, __VA_OPT__会将逗号和参数一起处理, 然后通过#__VA_ARGS__转换成一个字符串// A macro that substitutes with 1 if called without arguments, otherwise 0.
#define BASE_IS_EMPTY(...) BASE_INTERNAL_IS_EMPTY_EXPANDED(__VA_ARGS__)
#define BASE_INTERNAL_IS_EMPTY_EXPANDED(...) \BASE_INTERNAL_IS_EMPTY_INNER("_" __VA_OPT__(,) #__VA_ARGS__)
#define BASE_INTERNAL_IS_EMPTY_INNER(...) \BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(__VA_ARGS__, 0, 1)
#define BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(e0, e1, is_empty, ...) is_empty

这里通过"_" __VA_OPT__(,) #__VA_ARGS__来操作,将__VA_ARGS__当成整体变成字符串,那么就有:

BASE_IS_EMPTY(a, b, c, d) => BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED("_", "a, b, c, d", 0, 1)
>>> is_empty == 0, 符合预期
5. remove_parens.h

源码如下:

// A macro that removes at most one outer set of parentheses from its arguments.
// If the arguments are not surrounded by parentheses, this expands to the
// arguments unchanged. For example:
// `BASE_REMOVE_PARENS()` -> ``
// `BASE_REMOVE_PARENS(foo)` -> `foo`
// `BASE_REMOVE_PARENS(foo(1))` -> `foo(1)`
// `BASE_REMOVE_PARENS((foo))` -> `foo`
// `BASE_REMOVE_PARENS((foo(1)))` -> `foo(1)`
// `BASE_REMOVE_PARENS((foo)[1])` -> `(foo)[1]`
// `BASE_REMOVE_PARENS(((foo)))` -> `(foo)`
// `BASE_REMOVE_PARENS(foo, bar, baz)` -> `foo, bar, baz`
// `BASE_REMOVE_PARENS(foo, (bar), baz)` -> `foo, (bar), baz`
#define BASE_REMOVE_PARENS(...)                                            \BASE_IF(BASE_INTERNAL_IS_PARENTHESIZED(__VA_ARGS__), BASE_INTERNAL_ECHO, \BASE_INTERNAL_EMPTY())                                           \__VA_ARGS__#define BASE_INTERNAL_IS_PARENTHESIZED(...) \BASE_IS_EMPTY(BASE_INTERNAL_EAT __VA_ARGS__)
#define BASE_INTERNAL_EAT(...)
#define BASE_INTERNAL_ECHO(...) __VA_ARGS__
#define BASE_INTERNAL_EMPTY()

这段代码定义了一个名为BASE_REMOVE_PARENS的宏,用于移除宏参数最外层的一对括号(如果有的话)。如果参数没有被括号包围,则宏展开后的结果与原参数相同。

宏的实现利用了之前提到的BASE_IS_EMPTY和BASE_IF宏,以及一些辅助的内部宏:

  • BASE_INTERNAL_IS_PARENTHESIZED: 判断参数是否被括号包围。它将参数传递给BASE_INTERNAL_EAT宏,如果参数有括号,那么括号内的内容会被BASE_INTERNAL_EAT"吃掉",导致BASE_IS_EMPTY的结果为1,否则为0。
  • BASE_INTERNAL_EAT: 接受任意参数,但不做任何事情。
  • BASE_INTERNAL_ECHO: 原样返回传入的参数。
  • BASE_INTERNAL_EMPTY: 不接受任何参数,也不返回任何内容。

BASE_REMOVE_PARENS的实现可以分为两步:

  • 使用BASE_INTERNAL_IS_PARENTHESIZED判断参数是否有括号。
  • 根据第一步的结果,使用BASE_IF选择BASE_INTERNAL_ECHO(有括号)或BASE_INTERNAL_EMPTY(无括号),并将其展开。

最后,将原始的__VA_ARGS__附加在展开的结果之后。如果参数有括号,那么展开的空宏会移除最外层括号,否则原始参数不变。

下面是一个示例演示:

BASE_REMOVE_PARENS((foo))
>>> BASE_IF(BASE_INTERNAL_IS_PARENTHESIZED((foo))), BASE_INTERNAL_ECHO, BASE_INTERNAL_EMPTY()) (foo)
>>> BASE_IF(BASE_IS_EMPTY(BASE_INTERNAL_EAT ((foo))), BASE_INTERNAL_ECHO, BASE_INTERNAL_EMPTY()) (foo)
//这里__VA_ARGS__外面存在括号,则会执行BASE_INTERNAL_EAT宏,从而变成空,条件选择BASE_INTERNAL_ECHO
>>> BASE_INTERNAL_ECHO (foo)
>>> foo // 去掉了外面一层括号

相关文章:

chrome-base源码分析(1)macros模块

Chrome-base源码分析(2)之Macros模块 Author&#xff1a;Once Day Date&#xff1a;2024年6月29日 漫漫长路&#xff0c;才刚刚开始… 全系列文章请查看专栏: 源码分析_Once-Day的博客-CSDN博客 参考文档: macros - Chromium Code SearchChrome base 库详解&#xff1a;工…...

玩转springboot之springboot定制嵌入式的servlet

springboot定制嵌入式的servlet容器 修改容器配置 有两种方式可以修改容器的配置 可以直接在配置文件中修改和server有关的配置 server.port8081 server.tomcat.uri-encodingUTF-8//通用的Servlet容器设置 server.xxx //指定Tomcat的设置 server.tomcat.xxx编写一个EmbeddedSer…...

dell服务器RAID5磁盘阵列出现故障的解决过程二——热备盘制作与坏盘替换过程

目录 背景方案概念全局热备&#xff08;Global Hot Spare&#xff09;&#xff1a;独立热备&#xff08;Dedicated Hot Spare&#xff09;&#xff1a; 过程8号制作成热备清除配置制作独立热备热备顶替坏盘直接rebuild 更换2号盘2号热备 注意注意事项foreign状态要先清除配置 背…...

Elasticsearch开启认证|为ES设置账号密码|ES账号密码设置|ES单机开启认证|ES集群开启认证

文章目录 前言单节点模式开启认证生成节点证书修改ES配置文件为内置账号添加密码Kibana修改配置验证 ES集群开启认证验证 前言 ES安装完成并运行&#xff0c;默认情况下是允许任何用户访问的&#xff0c;这样并不安全&#xff0c;可以为ES开启认证&#xff0c;设置账号密码。 …...

Excel 数据筛选难题解决

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…...

Web实时通信的学习之旅:WebSocket入门指南及示例演示

文章目录 WebSocket的特点1、工作原理2、特点3、WebSocket 协议介绍4、安全性 WebSocket的使用一、服务端1、创建实例&#xff1a;创建一个webScoket实例对象1.1、WebSocket.Server(options[&#xff0c;callback])方法中options对象所支持的参数1.2、同样也有一个加密的 wss:/…...

分治精炼宝库-----快速排序运用(⌯꒪꒫꒪)੭

目录 一.基本概念: 一.颜色分类&#xff1a; 二.排序数组&#xff1a; 三.数组中的第k个最大元素&#xff1a; 解法一&#xff1a;快速选择算法 解法二&#xff1a;简单粗暴优先级队列 四.库存管理Ⅲ&#xff1a; 解法一&#xff1a;快速选择 解法二&#xff1a;简单粗…...

快速修复mfc100u.dll丢失解决方案

相连文章&#xff1a;SecureCRT的安装破解 [详细过程2024] 有小伙伴向我反馈在打开SecureFX注册机之后显示【mfc100u.dll找不到】重装之后也没有用&#xff0c;这个是因为Microsoft Visual C的运行时组件和库出现了错误&#xff0c;直接选择重新安装就可以 出现这种情况的原因…...

【C++深度探索】继承机制详解(一)

hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#xff1a;大耳朵土土垚的博客 &#x1…...

力扣第218题“天际线问题”

在本篇文章中&#xff0c;我们将详细解读力扣第218题“天际线问题”。通过学习本篇文章&#xff0c;读者将掌握如何使用扫描线算法和堆来解决这一问题&#xff0c;并了解相关的复杂度分析和模拟面试问答。每种方法都将配以详细的解释&#xff0c;以便于理解。 问题描述 力扣第…...

帝国cms未审核文章可视化预览效果

有时候为了让编辑更加清楚的看到别人审核之后的效果&#xff0c;同时文章有需要下一级审核才能在前端展示出来&#xff0c;今天就来展示一个未审核文章预览审核后的效果 这次给某出版社开发的时候&#xff0c;他们需要实现编辑能够预览自己发布之后的审核效果&#xff0c;所以就…...

医院管理系统带万字文档医院预约挂号管理系统基于spingboot和vue的前后端分离java项目java课程设计java毕业设计

文章目录 仓库管理系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档&#xff08;9.9&#xffe5;带走&#xff09; 仓库管理系统 一、项目演示 医院管理系统 二、项目介绍 基于springbootvue的前后端分离医院管…...

爬虫技术在物联网数据采集中的应用

爬虫技术在物联网数据采集中的应用案例主要包括以下几个方面&#xff1a; 电商平台数据采集&#xff1a;例如&#xff0c;使用Python编写的网络爬虫可以用于爬取京东网页相关数据&#xff0c;如品牌、标题、价格、店铺等&#xff0c;并进行数据处理及可视化展示。这种方法不仅可…...

spring boot初始化的几个总结

spring intializr File->New->Project 注意&#xff1a;Spring Initializer中 Java版本选择模块已经不支持1.8了。 Spring Boot 3.x要求 Java最低版本为17&#xff0c; 最新的SpringBoot版本已经要求Java22了 所以&#xff0c;你可以升级Java版本&#xff0c;使用Spri…...

springcloud第4季 seata报could not find any implementation for class

一 问题说明 1.1 描述 在使用seata2.0alibaba-cloud 2022.0.0.0-RC2nacos 2.2.3 模拟下订单分布式事务场景&#xff0c;出现如下问题&#xff1a;java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0 查看服务端&#xff1a;java.util.ServiceCo…...

IT之家最新科技热点

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…...

对象实例化过程

目录 一、Java对象实例化在JVM中的过程&#xff1a; 类加载与初始化 分配内存 初始化对象内存 设置对象头 执行初始化方法 构造方法执行 二、对象的创建过程 一、Java对象实例化在JVM中的过程&#xff1a; 类加载与初始化&#xff1a; 当JVM需要实例化一个对象时&#xff0c;它…...

常见漏洞之XSS

一、XSS简介 XSS&#xff08;Cross-Site Scripting&#xff0c;跨站脚本攻击&#xff09;是一种常见的网络攻击方式&#xff0c;通过在网页中注入恶意脚本&#xff0c;当其他用户浏览这些网页时&#xff0c;这些嵌入的恶意脚本会在其浏览器上执行&#xff0c;从而进行各种恶意…...

Python变量的命名规则与赋值方式

第二章&#xff1a;Python 基础语法 第一节&#xff1a;变量的命名规则与赋值方式 2.1.1 引言 在编程中&#xff0c;变量是存储数据的基本单元。变量的命名和赋值是编程语言中表达和操作数据的基础。了解和遵循变量命名规则对于编写清晰、可维护的代码至关重要。 2.1.2 变量…...

昇思25天学习打卡营第7天|网络构建

昇思25天学习打卡营第7天|网络构建 前言函数式自动微分函数与计算图微分函数与梯度计算Stop GradientAuxiliary data神经网络梯度计算 个人任务打卡&#xff08;读者请忽略&#xff09;个人理解与总结 前言 非常感谢华为昇思大模型平台和CSDN邀请体验昇思大模型&#xff01;从今…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

FFmpeg 低延迟同屏方案

引言 在实时互动需求激增的当下&#xff0c;无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作&#xff0c;还是游戏直播的画面实时传输&#xff0c;低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架&#xff0c;凭借其灵活的编解码、数据…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

如何更改默认 Crontab 编辑器 ?

在 Linux 领域中&#xff0c;crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用&#xff0c;用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益&#xff0c;允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...