从我的小研究来看,下面的两个函数是等价的(从性能的角度来看),因为即使是非常轻的优化(-O1
)也会导致相同的汇编代码。
代码1:
#define BIT_N (10)
extern unsigned int isBitSet;
unsigned int Foo() {
unsigned int res1 = 0;
if (isBitSet)
{
res1 |= ( 1u << BIT_N );
}
return res1;
}
代码2:
#define BIT_N (10)
extern unsigned int isBitSet;
unsigned int Foo() {
unsigned int res1 = 0;
res1 |= ( (!!isBitSet) << BIT_N );
return res1;
}
代码1和代码2的反汇编是相同的(在使用gcc 6.3编译x86_64+optimization
-O2
之后):Foo():
mov edx, DWORD PTR isBitSet[rip]
xor eax, eax
test edx, edx
setne al
sal eax, 10
ret
我个人更喜欢C版本的代码2,它看起来更干净。但它和代码1一样安全吗?因为我看到了一些潜在的陷阱,比如如果
isBitSet
是int
和BIT_N
31类型,那么代码res1 = ( (!!isBitSet) << BIT_N);
将导致未定义的行为。问题:
还有其他的陷阱吗?代码1真的比代码2安全吗?
如果是,是否有任何已知的方法可以使代码2更安全而不需要太多开销?
最佳答案
因为我看到了一些潜在的陷阱,比如isBitSet的类型是int和BIT
你已经经历了另一个陷阱,即你假设isBitSet
的类型很重要。C中的所有“逻辑”运算符,如!
返回值为1或0的类型int
。
因此,表达式(!!isBitSet) << BIT_N
对每个设计都是危险的,不应该使用,因为上面提到了未定义的行为。
不应该使用它的另一个原因是,程序员可能错误地认为表达式的结果类型是isBitSet
类型。因此,如果程序员编写一些依赖于无符号环绕的东西,比如:
((!!isBitSet) << BIT_N) + UINT16_C(something)
然后,这也会导致32位系统上未定义的behavior=integer溢出,因为
+
的左操作数是有符号的,这不是有意的。问题的根源是在一行上使用多个运算符。这几乎总是不好的做法,并可能导致许多错误。我的经验还表明,
!!
技巧的出现通常是可疑代码的一个标志。理想的、完全可移植的代码版本是:
uint32_t Foo (void)
{
uint32_t result;
if(isBitSet)
{
result = 1u << BIT_N;
}
else
{
result = 0;
}
return result;
}
或者如果你愿意,完全等同于:
uint32_t Foo (void)
{
return isBitSet ? 1u<<BIT_N : 0;
}