双重否定或位移位哪个更好?

Which is better, double negation or bitshift?

我需要一个布尔类型的函数来确定是否设置了变量位表示中的位。

所以如果 foo 的第四位是我想要检查的,我可以使函数 return

!!(foo & 0x8)  //0x8 = 1000b

(foo & 0x8) >> 3

得到 0 或 1。

性能和便携性哪个更好?我正在开发一个小型嵌入式系统,因此可检测到的成本差异仍然很重要。

这个解决方案

return (foo & 0x8) >> 3;

最差。例如,如果魔术常量 0x8 将被更改,那么您还需要更改魔术常量 3。此外,它可能会以这样一种方式发生,即当您需要检查不止一位时,应用运算符 >> 将是不可能的。

如果你想 return 1(逻辑真)或 0(逻辑假)我认为写

会更清楚
return (foo & 0x8) != 0;

return (foo & 0x8) == 0x8;

例如,如果您将使用命名常量(或变量)代替魔术常量 0x8,例如 MASK,则此 return 语句

return ( foo & MASK ) == MASK; 

不依赖于 MASK 的值。

注意这两个return语句

return (foo & MASK) != 0;

return ( foo & MASK ) == MASK; 

不等价。第一个 return 语句意味着在变量 foo 中至少设置了一个位,而第二个 return 语句意味着正好设置了与 MASK 中的位相对应的所有位。

如果函数的return类型为_Bool(或<stdbool.h>中定义的bool),需要检查是否至少设置了一位到位掩码然后你可以写

return foo & MASK;

Which one is more desirable in terms of performance or portability?

对于性能,这完全取决于您的编译器如何优化函数。比如编译这两个函数:

#include <stdint.h>

uint32_t not_not(uint32_t foo)
{
    return !!(foo & 0x8);
}

uint32_t bit_shift(uint32_t foo)
{
    return (foo & 0x8) >> 3;
}

使用 x64 GCC 11.1 编译成完全相同的程序集 -O3 (link):

not_not:
        mov     eax, edi
        shr     eax, 3
        and     eax, 1
        ret
bit_shift:
        mov     eax, edi
        shr     eax, 3
        and     eax, 1
        ret

所以您应该检查您正在使用的任何编译器在您首选的优化级别生成的程序集,看看它们中的任何一个是否比另一个更快。

至于可移植性,考虑到在某些情况下您可能必须将位掩码更改为其他值,!! 可能是更安全的选择,因为更改位掩码不会强制您更改移位量以及。您也可以使用来自莫斯科的弗拉德建议的建议。

两者都会生成非常相似的代码,因此在性能方面完全(或几乎完全)相同。

程序员使用了很多年的双重否定,(IMO)它清楚地表明了程序员的意图。如果你有第二班,你需要花更多的时间阅读代码。它也更容易出错,因为人类会犯愚蠢的错误(例如错别字)。你能不加考虑地简单告诉我(x & 0x8000000000000) >> 52是否正确吗?

(x & 0x1000000000000) >> 48) 不如 !!(x & 0x1000000000000)

清楚

在性能方面,即使有差异也不会有太大差异。就可读性而言,您不应该使用任何一种形式。

... if the fourth bit of foo is what I want to inspect...

...那么您应该使用第四位作为输入。位 3 是第四位,因为位是从 0 开始计数的:

uint32_t n = 3;
if(foo & (1u << n))

这是迄今为止在 C 中屏蔽位的最常用方法。如果您需要值 1 或 0,则可以使用 !! 或仅使用 _Bool b = foo & (1u << n);.

我不知道性能(也许是基准?),但这里还有两个想法:

(x >> BITNUM) & 1

这使用一位移位和一个二进制与。作为奖励,您指定要查看的位的 NUMBER,而不是其他神奇的常量。相当容易使用。

(x & MASK) != 0

这使用一个二进制 AND 和一个与零的比较。 AFAIK 与零比较是大多数处理器上的特殊操作,因此它应该很便宜。甚至有可能该结果由处理器自动计算为二进制 AND 的副产品,并存储在 CPU 标志中。如果是这样,那么与零的比较可能会完全优化,只剩下一个按位与(取决于 CPU、编译器和其余代码)。

最后但同样重要的是,如果您只是将它用于 IF 语句,那么也许您真的不需要将它强制为 1 和 0?我的意思是,在 C 中任何非 0 的东西都是真实的,所以这个:

if (x & MASK)

可以正常工作并生成最佳代码。