将位与布尔值进行比较
Comparing a bit to a boolean
假设我有一组标志,编码为 uint16_t flags
。例如,AMAZING_FLAG = 0x02
。
现在,我有一个功能。这个函数需要检查我是否想改变标志,因为如果我想那样做,我需要写入闪存。那是昂贵的。因此,我想要一个检查,告诉我 flags & AMAZING_FLAG
是否等于 doSet
。这是第一个想法:
setAmazingFlag(bool doSet)
{
if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0)) {
// Really expensive thing
// Update flags
}
}
这不是一个直观的 if 语句。
我觉得应该有更好的方法,比如:
if ((flags & AMAZING_FLAG) != doSet){
}
但这实际上行不通,true
似乎等于0x01
。
那么,有没有一种巧妙的方法可以将位与布尔值进行比较?
从逻辑的角度来看,flags & AMAZING_FLAG
只是一个掩码所有其他标志的位操作。结果是一个数值。
要接收布尔值,您可以使用比较
(flags & AMAZING_FLAG) == AMAZING_FLAG
现在可以将此逻辑值与 doSet
进行比较。
if (((flags & AMAZING_FLAG) == AMAZING_FLAG) != doSet)
在 C 中可能会有缩写,因为数字到布尔值的隐式转换规则。所以你也可以写
if (!(flags & AMAZING_FLAG) == doSet)
写得更简洁。但是前一个版本在可读性方面更好。
要将任何非零数转换为 1(真),有一个老技巧:应用 !
(非)运算符两次。
if (!!(flags & AMAZING_FLAG) != doSet){
您需要将位掩码转换为布尔语句,在 C 中相当于值 0
或 1
。
(flags & AMAZING_FLAG) != 0
。最常见的方式。
!!(flags & AMAZING_FLAG)
。有点普通,也可以用,就是有点神秘。
(bool)(flags & AMAZING_FLAG)
。仅限 C99 及更高版本的现代 C 方式。
采用上述任何备选方案,然后使用 !=
或 ==
.
将其与您的布尔值进行比较
您可以根据 doSet
值创建掩码:
#define AMAZING_FLAG_IDX 1
#define AMAZING_FLAG (1u << AMAZING_FLAG_IDX)
...
uint16_t set_mask = doSet << AMAZING_FLAG_IDX;
现在您的支票可以如下所示:
setAmazingFlag(bool doSet)
{
const uint16_t set_mask = doSet << AMAZING_FLAG_IDX;
if (flags & set_mask) {
// Really expensive thing
// Update flags
}
}
在某些体系结构上,!!
可能会被编译为一个分支,这样,您可能有两个分支:
- 标准化
!!(expr)
- 比较
doSet
我的方案的优点是保证单支。
注意: 确保您不会通过左移超过 30 位(假设整数为 32 位)而引入未定义的行为。这可以通过 static_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");
轻松实现
有多种方法可以执行此测试:
三元运算符可能会产生代价高昂的跳转:
if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0))
您也可以使用布尔转换,这可能有效也可能无效:
if (!!(flags & AMAZING_FLAG) != doSet)
或其等效替代方案:
if (((flags & AMAZING_FLAG) != 0) != doSet)
如果乘法很便宜,你可以避免跳转:
if ((flags & AMAZING_FLAG) != doSet * AMAZING_FLAG)
如果 flags
是无符号的并且编译器非常聪明,下面的除法可能会编译成一个简单的移位:
if ((flags & AMAZING_FLAG) / AMAZING_FLAG != doSet)
如果架构使用二进制补码算法,这里是另一种选择:
if ((flags & AMAZING_FLAG) != (-doSet & AMAZING_FLAG))
或者,可以将 flags
定义为具有位域的结构,并使用更简单易读的语法:
if (flags.amazing_flag != doSet)
唉,这种方法通常不受欢迎,因为位域的规范不允许对位级实现进行精确控制。
假设我有一组标志,编码为 uint16_t flags
。例如,AMAZING_FLAG = 0x02
。
现在,我有一个功能。这个函数需要检查我是否想改变标志,因为如果我想那样做,我需要写入闪存。那是昂贵的。因此,我想要一个检查,告诉我 flags & AMAZING_FLAG
是否等于 doSet
。这是第一个想法:
setAmazingFlag(bool doSet)
{
if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0)) {
// Really expensive thing
// Update flags
}
}
这不是一个直观的 if 语句。 我觉得应该有更好的方法,比如:
if ((flags & AMAZING_FLAG) != doSet){
}
但这实际上行不通,true
似乎等于0x01
。
那么,有没有一种巧妙的方法可以将位与布尔值进行比较?
从逻辑的角度来看,flags & AMAZING_FLAG
只是一个掩码所有其他标志的位操作。结果是一个数值。
要接收布尔值,您可以使用比较
(flags & AMAZING_FLAG) == AMAZING_FLAG
现在可以将此逻辑值与 doSet
进行比较。
if (((flags & AMAZING_FLAG) == AMAZING_FLAG) != doSet)
在 C 中可能会有缩写,因为数字到布尔值的隐式转换规则。所以你也可以写
if (!(flags & AMAZING_FLAG) == doSet)
写得更简洁。但是前一个版本在可读性方面更好。
要将任何非零数转换为 1(真),有一个老技巧:应用 !
(非)运算符两次。
if (!!(flags & AMAZING_FLAG) != doSet){
您需要将位掩码转换为布尔语句,在 C 中相当于值 0
或 1
。
(flags & AMAZING_FLAG) != 0
。最常见的方式。!!(flags & AMAZING_FLAG)
。有点普通,也可以用,就是有点神秘。(bool)(flags & AMAZING_FLAG)
。仅限 C99 及更高版本的现代 C 方式。
采用上述任何备选方案,然后使用 !=
或 ==
.
您可以根据 doSet
值创建掩码:
#define AMAZING_FLAG_IDX 1
#define AMAZING_FLAG (1u << AMAZING_FLAG_IDX)
...
uint16_t set_mask = doSet << AMAZING_FLAG_IDX;
现在您的支票可以如下所示:
setAmazingFlag(bool doSet)
{
const uint16_t set_mask = doSet << AMAZING_FLAG_IDX;
if (flags & set_mask) {
// Really expensive thing
// Update flags
}
}
在某些体系结构上,!!
可能会被编译为一个分支,这样,您可能有两个分支:
- 标准化
!!(expr)
- 比较
doSet
我的方案的优点是保证单支。
注意: 确保您不会通过左移超过 30 位(假设整数为 32 位)而引入未定义的行为。这可以通过 static_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");
有多种方法可以执行此测试:
三元运算符可能会产生代价高昂的跳转:
if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0))
您也可以使用布尔转换,这可能有效也可能无效:
if (!!(flags & AMAZING_FLAG) != doSet)
或其等效替代方案:
if (((flags & AMAZING_FLAG) != 0) != doSet)
如果乘法很便宜,你可以避免跳转:
if ((flags & AMAZING_FLAG) != doSet * AMAZING_FLAG)
如果 flags
是无符号的并且编译器非常聪明,下面的除法可能会编译成一个简单的移位:
if ((flags & AMAZING_FLAG) / AMAZING_FLAG != doSet)
如果架构使用二进制补码算法,这里是另一种选择:
if ((flags & AMAZING_FLAG) != (-doSet & AMAZING_FLAG))
或者,可以将 flags
定义为具有位域的结构,并使用更简单易读的语法:
if (flags.amazing_flag != doSet)
唉,这种方法通常不受欢迎,因为位域的规范不允许对位级实现进行精确控制。