bitwise not操作的编译器优化

Compiler optimization of bitwise not operation

我有一个简单的函数来测试两个数组是否互为逆数组。 除了 tmp 变量外,它们看起来完全相同。一个有效,另一个无效。我一辈子都弄不明白为什么编译器会优化它——如果它确实是一个优化问题(我的编译器是 IAR Workbench v4.30.1)。这是我的代码:

// this works as expected
uint8 verifyInverseBuffer(uint8 *buf, uint8 *bufi, uint32 len)
{
  uint8 tmp;
  for (uint32 i = 0; i < len; i++)
  {
    tmp = ~bufi[i];
    if (buf[i] != tmp)
    {
      return 0;
    }
  }
  return 1;  
}

// this does NOT work as expected (I only removed the tmp!)
uint8 verifyInverseBuffer(uint8 *buf, uint8 *bufi, uint32 len)
{
  for (uint32 i = 0; i < len; i++)
  {
    if (buf[i] != (~bufi[i]))
    {
      return 0;
    }
  }
  return 1;  
}

代码的第一个版本有效,第二个版本无效。谁能弄清楚为什么?还是来个测试看看哪里出了问题?

您看到的情况是 整数提升 规则的结果。任何时候在表达式中使用小于 int 的变量,该值都会被提升为类型 int

假设 bufi[i] 包含值 255。其十六进制表示为 0xFF。该值然后是 ~ 运算符的操作数。因此,该值将首先被提升为 int,它(假设它是 32 位)将具有值 0x000000FF,并将 ~ 应用于此得到 0xFFFFFF00。然后将此值与类型为 uint8_tbuf[i] 进行比较。值 0xFFFFFF00 超出此范围,因此比较始终为假。

如果将 ~ 的结果赋值回 uint8_t 类型的变量,值 0xFFFFFF00 将转换为 0x00。然后将此转换后的值与 buf[i].

进行比较

所以你看到的行为不是优化的结果,而是语言的规则。按原样使用临时变量是解决此问题的一种方法。您还可以将结果转换为 uint8:

if(buf[i] != (uint8)(~bufi[i]))

或者屏蔽除最低位字节以外的所有字节:

if(buf[i] != (~bufi[i] & 0xff))

问题是整数提升。 ~ 运算符非常危险!

~bufi[i] 的情况下,~ 的操作数根据整数提升进行提升。使代码等同于 ~(int)bufi[i].

所以在第二种情况下 buf[i] != (~bufi[i]) 你会得到类似 0xXX != 0xFFFFFFFFYY 的东西,其中 "XX" 和 "YY" 是你想要比较的实际值,而 0xFFFF 是无意的废话通过取 int 的按位补码放置在那里。这将始终评估为 true,因此编译器可能会优化部分代码,从而产生一个非常微妙的错误。

tmp = ~bufi[i]; 的情况下,您可以通过将 0xFFFFFFFFYY 截断为 "YY" 您感兴趣的值来避免此错误。

有关详细信息,请参阅 。还可以考虑采用 MISRA-C 来避免像这样的细微错误。

正如 Lundin 和 dbush 已经指出的,第二个版本中的比较总是失败,因为提升为 int 的任何 uint8 值的相反值与所有 uint8 值不同。也就是说,第二个版本相当于:

// this does NOT work as expected (I only removed the tmp!)
uint8 verifyInverseBuffer(uint8 *buf, uint8 *bufi, uint32 len) {
    if (len) return 0;
    return 1;
}

正如在 Godbolt's compiler explorer 上看到的那样,gccclang 都检测到了这一点并完全优化了代码:

verifyInverseBuffer:
    test    edx, edx
    sete    al
    ret

gcc 产生一个相当神秘的警告,指出一个可疑的 signed/unsigned 比较问题,这不是真正的问题...关闭但没有香蕉。

<source>: In function 'verifyInverseBuffer':
<source>:8:16: warning: comparison of promoted bitwise complement of an unsigned value with unsigned [-Wsign-compare]
    8 |     if (buf[i] != (~bufi[i]))
      |                ^~
Compiler returned: 0