问题描述
我需要一个布尔类型的函数来确定变量的位表示中的某个位是否被设置。
所以如果 foo
的第四位是我想要检查的,我可以让函数返回
!!(foo & 0x8) //0x8 = 1000b
或
(foo & 0x8) >> 3
得到 0 或 1。
在性能或便携性方面哪个更受欢迎?我正在开发一个小型嵌入式系统,所以一点但可检测的成本差异仍然很重要。
解决方法
这个解决方案
return (foo & 0x8) >> 3;
是最糟糕的。例如,如果魔术常数 0x8
将被更改,那么您还需要更改魔术常数 3
。此外,可能会发生这样的情况,例如当您需要检查多个位时,应用运算符 >> 将是不可能的。
如果你想返回 1(逻辑真)或 0(逻辑假)我认为如果这样写会更清楚
return (foo & 0x8) != 0;
或
return (foo & 0x8) == 0x8;
例如,如果您将使用命名常量(或变量)代替魔术常量 0x8,例如 MASK,则此返回语句
return ( foo & MASK ) == MASK;
不会依赖于 MASK 的值。
注意这两个返回语句
return (foo & MASK) != 0;
和
return ( foo & MASK ) == MASK;
不等价。第一个 return 语句意味着在变量 foo
中至少设置了一个位,而第二个 return 语句意味着与 MASK 中的位对应的所有位都设置了。
如果函数的返回类型是_Bool
(或bool
中定义的<stdbool.h>
),并且需要根据位掩码检查是否至少设置了一位,那么你只能写
return foo & MASK;
,
在性能或便携性方面哪个更受欢迎?
对于性能,这完全取决于您的编译器如何优化函数。比如编译这两个函数:
#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,1
ret
因此,您应该检查由您在首选优化级别使用的任何编译器生成的程序集,以查看它们中的任何一个是否比另一个更快。
至于可移植性,考虑到在某些情况下您可能需要将位掩码更改为其他值,!!
可能是更安全的选择,因为更改位掩码也不会强制您更改移位量.您也可以使用来自莫斯科的弗拉德提出的建议。
两者都将生成非常相似的代码,因此在性能方面完全(或几乎完全)相同。
双重否定已被程序员使用多年,(IMO)它清楚地表明了程序员的意图。如果您有第二个班次,则需要花更多时间阅读代码。它也更容易出错,因为人类会犯愚蠢的错误(例如拼写错误)。您能不加考虑地轻松告诉我 (x & 0x8000000000000) >> 52
是否正确吗?
(x & 0x1000000000000) >> 48)
不如!!(x & 0x1000000000000)
性能方面不会有太大差异(如果有的话)。在可读性方面,您不应使用任何一种形式。
...如果 foo 的第四位是我想要检查的...
...那么你应该使用第四位作为输入。位 3 是第四位,因为位从 0 开始计数:
uint32_t n = 3;
if(foo & (1u << n))
这是迄今为止在 C 中屏蔽位的最常用方法。如果您需要值 1 或 0,那么您可以使用 !!
或仅使用 _Bool b = foo & (1u << n);
。
我不知道性能(可能是基准测试?),但还有两个想法:
(x >> BITNUM) & 1
这使用一位移位和一位二进制 AND。作为奖励,您可以指定要查看的位的 NUMBER,而没有其他神奇的常量。非常容易使用。
(x & MASK) != 0
这使用一个二进制 AND 和一个与零的比较。 AFAIK 与零比较是大多数处理器上的特殊操作,因此它应该很便宜。这个结果甚至有可能由处理器作为二进制 AND 的副产品自动计算并存储在 CPU 标志中。如果是这样,那么与零的比较可能会完全优化,只剩下一个按位 AND(不过取决于 CPU、编译器和其余代码)。
最后但并非最不重要的一点是,如果您只是将它用于 IF 语句,那么也许您真的不需要将其强制为 1 和 0?我的意思是,在 C 中任何非 0 都是真实的,所以这个:
if (x & MASK)
可以正常工作并生成最佳代码。