从我的小研究来看,下面的两个函数是等价的(从性能的角度来看),因为即使是非常轻的优化(-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一样安全吗?因为我看到了一些潜在的陷阱,比如如果isBitSetintBIT_N31类型,那么代码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;
}

10-08 14:48