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
源码如下:
// 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_1
和base_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 // 去掉了外面一层括号
发表评论