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

嚴華  金牌会员 | 2024-7-12 22:03:21 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 548|帖子 548|积分 1644

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)

  
1. 概述

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

源码如下:
  1. // A macro that expands to the concatenation of its arguments. If the arguments
  2. // are themselves macros, they are first expanded (due to the indirection
  3. // through a second macro). This can be used to construct tokens.
  4. #define BASE_CONCAT(a, b) BASE_INTERNAL_CONCAT(a, b)
  5. // Implementation details: do not use directly.
  6. #define BASE_INTERNAL_CONCAT(a, b) a##b
复制代码
这个非常底子的关键字拼接宏,例如下面所示:
  1. TEST(MacrosTest, Concat) {
  2.   auto a = BASE_CONCAT(10, 20);
  3.   std::cout << "a: " << a << std::endl;
  4.   EXPECT_EQ(a, 1020);
  5.   auto b = BASE_CONCAT(5000, 6000);
  6.   std::cout << "b: " << b << std::endl;
  7.   EXPECT_EQ(b, 50006000);
  8. }
  9. >>> a: 1020
  10. >>> b: 50006000
复制代码
BASE_CONCAT(10, 20)会输出1020,这是可以作为源码字面量的值,并不是"10" + "20" = "1020"这种字符串拼接。
3. if.h

源码如下:
  1. // Given a `_Cond` that evaluates to exactly 0 or 1, this macro evaluates to
  2. // either the `_Then` or `_Else` args. Unlike a real conditional expression,
  3. // this does not support conditions other than `0` and `1`.
  4. #define BASE_IF(_Cond, _Then, _Else) \
  5.   BASE_CONCAT(BASE_INTERNAL_IF_, _Cond)(_Then, _Else)
  6. // Implementation details: do not use directly.
  7. #define BASE_INTERNAL_IF_1(_Then, _Else) _Then
  8. #define BASE_INTERNAL_IF_0(_Then, _Else) _Else
复制代码
这段代码定义了一个名为BASE_IF的宏,用于实现编译期的条件选择功能。
宏接受三个参数:_Cond、_Then和_Else。_Cond必须是一个计算结果为0或1的表达式。
根据_Cond的值,宏会将其睁开为_Then或_Else参数的内容。
宏的实现依靠于两个内部宏BASE_INTERNAL_IF_1和BASE_INTERNAL_IF_0,它们分别选择_Then和_Else参数。
通过巧妙的宏拼接,BASE_IF可以或许在编译期根据条件选择代码,而不会产生运行时开销。
例如下面所示:
  1. TEST(MacrosTest, If) {
  2.   auto a = BASE_IF(1, 10, 20);
  3.   std::cout << "a: " << a << std::endl;
  4.   EXPECT_EQ(a, 10);
  5.   auto b = BASE_IF(0, 100, 200);
  6.   std::cout << "b: " << b << std::endl;
  7.   EXPECT_EQ(b, 200);
  8. }
  9. >>> a: 10
  10. >>> b: 200
复制代码
4. is_empty.h

源码如下:
  1. // A macro that substitutes with 1 if called without arguments, otherwise 0.
  2. #define BASE_IS_EMPTY(...) BASE_INTERNAL_IS_EMPTY_EXPANDED(__VA_ARGS__)
  3. #define BASE_INTERNAL_IS_EMPTY_EXPANDED(...) \
  4.   BASE_INTERNAL_IS_EMPTY_INNER(_, ##__VA_ARGS__)
  5. #define BASE_INTERNAL_IS_EMPTY_INNER(...) \
  6.   BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(__VA_ARGS__, 0, 1)
  7. #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可以或许在编译期判断宏调用时是否传递了参数。下面是一些示例:
  1. BASE_IS_EMPTY()      // 展开为 1
  2. BASE_IS_EMPTY(a)     // 展开为 0
  3. BASE_IS_EMPTY(a, b)  // 展开为 0
复制代码
这个宏常用于其他宏定义中,用于根据传参情况天生差别的代码,或者举行静态断言查抄宏参数等。好比可以写一个字符串毗连的宏:
  1. #define CONCAT(a, ...) \
  2.   BASE_IF(BASE_IS_EMPTY(__VA_ARGS__), a, CONCAT_INNER(a, __VA_ARGS__))
  3. // 当只传一个参数时直接返回,多个参数时递归拼接  
复制代码
在windows上编译时,需要传入/Zc:preprocessor参数,以确保C++预编译器的行为和GCC一致。
windows上默认行为比较特殊,如下:
  1. // windows行为
  2. BASE_IS_EMPTY(a, b, c, d)
  3. >>> BASE_INTERNAL_IS_EMPTY_EXPANDED(a, b, c, d)
  4. >>> BASE_INTERNAL_IS_EMPTY_INNER(_, a, b, c, d)
  5. >>> BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(_, a, b, c, d, 0, 1)
  6. e0 =  _, a, b, c, d  //因为windows默认把__VA_ARGS__当成整体了,这与GCC行为存在差异
  7. e1 =  0
  8. e2 =  1
复制代码
GCC的默认行为如下:
  1. // GCC行为
  2. BASE_IS_EMPTY(a, b, c, d)
  3. >>> BASE_INTERNAL_IS_EMPTY_EXPANDED(a, b, c, d)
  4. >>> BASE_INTERNAL_IS_EMPTY_INNER(_, a, b, c, d)
  5. >>> BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(_, a, b, c, d, 0, 1)
  6. e0 =  _
  7. e1 =  a # GCC上, 默认是按照展开位置来抉择
  8. e2 =  b
  9. ......
复制代码
虽然GCC的行为是正常睁开了变量,参数和位置能一一对应,但是依旧不满足逻辑,如下:
  1. // BASE_IS_EMPTY() 应该返回 1
  2. BASE_IS_EMPTY() => BASE_INTERNAL_IS_EMPTY_INNER(_) => BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(_, 0, 1)
  3. >>> 所以is_empty == 1
  4. // BASE_IS_EMPTY(a, b, c, d) 应该返回0
  5. BASE_IS_EMPTY(a, b, c, d) => BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(_, a, b, c, d, 0, 1)
  6. >>> 所以is_empty == b, 这并不符合预期
复制代码
所以需要对下令稍加改造:
  1. // 在Windows上使用msvc时, 需要/Zc:preprocessor参数, 否则宏处理会有问题
  2. // reference: https://stackoverflow.com/questions/77700691/getting-va-opt-to-be-recognized-by-visual-studio
  3. // 对于多个参数的情况, __VA_OPT__会将逗号和参数一起处理, 然后通过#__VA_ARGS__转换成一个字符串
  4. // A macro that substitutes with 1 if called without arguments, otherwise 0.
  5. #define BASE_IS_EMPTY(...) BASE_INTERNAL_IS_EMPTY_EXPANDED(__VA_ARGS__)
  6. #define BASE_INTERNAL_IS_EMPTY_EXPANDED(...) \
  7.   BASE_INTERNAL_IS_EMPTY_INNER("_" __VA_OPT__(,) #__VA_ARGS__)
  8. #define BASE_INTERNAL_IS_EMPTY_INNER(...) \
  9.   BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(__VA_ARGS__, 0, 1)
  10. #define BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED(e0, e1, is_empty, ...) is_empty
复制代码
这里通过"_" __VA_OPT__(,) #__VA_ARGS__来操作,将__VA_ARGS__当成整体变成字符串,那么就有:
  1. BASE_IS_EMPTY(a, b, c, d) => BASE_INTERNAL_IS_EMPTY_INNER_EXPANDED("_", "a, b, c, d", 0, 1)
  2. >>> is_empty == 0, 符合预期
复制代码
5. remove_parens.h

源码如下:
  1. // A macro that removes at most one outer set of parentheses from its arguments.
  2. // If the arguments are not surrounded by parentheses, this expands to the
  3. // arguments unchanged. For example:
  4. // `BASE_REMOVE_PARENS()` -> ``
  5. // `BASE_REMOVE_PARENS(foo)` -> `foo`
  6. // `BASE_REMOVE_PARENS(foo(1))` -> `foo(1)`
  7. // `BASE_REMOVE_PARENS((foo))` -> `foo`
  8. // `BASE_REMOVE_PARENS((foo(1)))` -> `foo(1)`
  9. // `BASE_REMOVE_PARENS((foo)[1])` -> `(foo)[1]`
  10. // `BASE_REMOVE_PARENS(((foo)))` -> `(foo)`
  11. // `BASE_REMOVE_PARENS(foo, bar, baz)` -> `foo, bar, baz`
  12. // `BASE_REMOVE_PARENS(foo, (bar), baz)` -> `foo, (bar), baz`
  13. #define BASE_REMOVE_PARENS(...)                                            \
  14.   BASE_IF(BASE_INTERNAL_IS_PARENTHESIZED(__VA_ARGS__), BASE_INTERNAL_ECHO, \
  15.           BASE_INTERNAL_EMPTY())                                           \
  16.   __VA_ARGS__
  17. #define BASE_INTERNAL_IS_PARENTHESIZED(...) \
  18.   BASE_IS_EMPTY(BASE_INTERNAL_EAT __VA_ARGS__)
  19. #define BASE_INTERNAL_EAT(...)
  20. #define BASE_INTERNAL_ECHO(...) __VA_ARGS__
  21. #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__附加在睁开的结果之后。如果参数有括号,那么睁开的空宏会移除最外层括号,否则原始参数不变。
下面是一个示例演示:
  1. BASE_REMOVE_PARENS((foo))
  2. >>> BASE_IF(BASE_INTERNAL_IS_PARENTHESIZED((foo))), BASE_INTERNAL_ECHO, BASE_INTERNAL_EMPTY()) (foo)
  3. >>> BASE_IF(BASE_IS_EMPTY(BASE_INTERNAL_EAT ((foo))), BASE_INTERNAL_ECHO, BASE_INTERNAL_EMPTY()) (foo)
  4. //这里__VA_ARGS__外面存在括号,则会执行BASE_INTERNAL_EAT宏,从而变成空,条件选择BASE_INTERNAL_ECHO
  5. >>> BASE_INTERNAL_ECHO (foo)
  6. >>> foo // 去掉了外面一层括号
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

嚴華

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表