当前位置: 代码网 > it编程>操作系统>苹果Mac > chrome-base源码分析(1)macros模块

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

2024年07月31日 苹果Mac 我要评论
chrome-base源码中macros模块是一个比较简单的模块,定义了一些简单的宏,有五个源文件,下面一一介绍。

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

author:once day date:2024年6月29日

漫漫长路,才刚刚开始…

全系列文章请查看专栏: 源码分析_once-day的博客-csdn博客

参考文档:

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 // 去掉了外面一层括号
(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com