比较一下布尔值


12

假设我有一组以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

因此,有没有一种比较布尔值的好方法?


2
尚不清楚您的逻辑需要是什么。是这个(flags & AMAZING_FLAG) && doSet吗?
kaylum

问题尚不清楚。我们要检查“标志”是否为0x01。那是你想要的吗?如果是,那么我们可以使用按位运算符“&”。
Vikas Vijayan

如果只想在doSet为true时调用setAmazingFlag使其更具可读性,则函数名称会更好地检出,否则您的函数可能无法执行或不执行其名称说明,从而导致代码读取错误
AndersK

如果您想做的只是使其更具可读性,只需创建一个函数flagsNotEqual即可。
Jeroen3

Answers:


18

要将一个非零数字转换为1(true),有一个古老的技巧:!两次应用(not)运算符。

if (!!(flags & AMAZING_FLAG) != doSet){

4
或者(bool)(flags & AMAZING_FLAG) != doSet-我觉得更直接。(尽管表明微软先生对此有疑问。)而且,((flags & AMAZING_FLAG) != 0)可能正是它所编译的内容并且是绝对明确的。
克里斯·霍尔

“而且(((flags&AMAZING_FLAG)!= 0)可能正是它编译的对象,并且是绝对明确的”。会吗 如果doSet为true,该怎么办?
Cheiron

@ChrisHall(bool)2在C中是1还是2?
user253751

3
C11 Standard,6.3.1.2布尔类型:“将任何标量值转换为_Bool时,如果该值等于0,则结果为0;否则,结果为1。” 6.5.4和Cast运算符:“在表达式前面加上括号类型名称会将表达式的值转换为命名类型。”
克里斯·霍尔

@Cheiron,更明确的说:((bool)(flags & AMAZING_FLAG) != doSet)具有相同的效果(((flags & AMAZING_FLAG) != 0) != doSet),它们都可能编译为完全相同的东西。建议(!!(flags & AMAZING_FLAG) != doSet)是等效的,我想也可以编译成相同的东西。您认为这完全是一种品味问题:(bool)演员表要求您记住演员表和_Bool转换的工作方式;在!!需要其他一些心理体操,或者你就知道了“帽子戏法”。
克里斯·霍尔

2

您需要将位掩码转换为布尔型语句,在C中等效于01

  • (flags & AMAZING_FLAG) != 0。最常见的方式。

  • !!(flags & AMAZING_FLAG)。有点普通,也可以使用,但有点神秘。

  • (bool)(flags & AMAZING_FLAG)。仅限C99和更高版本的现代C语言。

采取以上任何一种方法,然后使用!=或将其与布尔值进行比较==


1

从逻辑的角度来看,flags & AMAZING_FLAG仅是位操作掩盖了所有其他标志。结果是一个数值。

要获得布尔值,您可以使用比较

(flags & AMAZING_FLAG) == AMAZING_FLAG

现在可以将此逻辑值与进行比较doSet

if (((flags & AMAZING_FLAG) == AMAZING_FLAG) != doSet)

在C中可能会有缩写,因为数字到布尔值的隐式转换规则。所以你也可以写

if (!(flags & AMAZING_FLAG) == doSet)

写得更简洁。但是从可读性方面来说,前一个版本更好。


1

您可以基于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");


1
所有形式的布尔表达式都有可能导致分支。在应用奇怪的手动优化技巧之前,我将首先进行分解。
伦丁

1
@Lundin,当然,这就是为什么我写“关于某些体系结构”的原因……
Alex Lop。

1
这主要是编译器的优化器的事,而不是ISA本身的事。
伦丁

1
@伦丁我不同意。有些体系结构具有用于条件位设置/重置的ISA,在这种情况下不需要分支,而其他则不需要。godbolt.org/z/4FDzvw
Alex Lop。

注意:如果你这样做,请确定AMAZING_FLAG在以下方面AMAZING_FLAG_IDX(例如#define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))),所以您不必在两个地方,使得人们可以进行更新(比如,从定义相同的数据0x40x8),而其他(IDX2)保持不变。
ShadowRanger

-1

有多种方法可以执行此测试:

三元运算符可能会产生代价高昂的跳转:

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为unsigned并且编译器非常聪明,则下面的部分可能会编译为简单的移位:

if ((flags & AMAZING_FLAG) / AMAZING_FLAG != doSet)

如果体系结构使用二进制补码算法,则这是另一种选择:

if ((flags & AMAZING_FLAG) != (-doSet & AMAZING_FLAG))

或者,可以将其定义flags为具有位域的结构,并使用一种更简单的可读语法:

if (flags.amazing_flag != doSet)

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


任何代码段的主要目标应该是可读性,而大多数示例都不是。当您将示例实际加载到编译器中时,会出现一个更大的问题:最初的两个示例实现具有最佳性能:最小的跳转和加载量(ARM 8.2,-Os)(它们是等效的)。所有其他实现均表现较差。通常,应该避免做一些花哨的事情(微优化),因为这样会使编译器更难弄清发生了什么,这会降低性能。因此,-1。
Cheiron
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.