将位与布尔值进行比较

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 中相当于值 01

  • (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
    }
}

在某些体系结构上,!! 可能会被编译为一个分支,这样,您可能有两个分支:

  1. 标准化!!(expr)
  2. 比较 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)

唉,这种方法通常不受欢迎,因为位域的规范不允许对位级实现进行精确控制。