编译器按位不运算的优化

Sup*_*pAl 31 c c++ embedded iar

我有一个简单的函数测试,如果两个数组彼此相反。它们似乎是相同的,只是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;  
}
Run Code Online (Sandbox Code Playgroud)

该代码的第一个版本有效,而第二个则无效。谁能找出原因?还是附带一些测试来探查出什么问题?

dbu*_*ush 43

您看到的是整数促销规则的结果。每当int在表达式中使用小于的变量时,值都会升为type int

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

如果将~back 的结果分配给type变量uint8_t,则该值0xFFFFFF00将转换为0x00。然后将此转换后的值与进行比较buf[i]

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

if(buf[i] != (uint8)(~bufi[i]))
Run Code Online (Sandbox Code Playgroud)

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

if(buf[i] != (~bufi[i] & 0xff))
Run Code Online (Sandbox Code Playgroud)

  • 我认为`buf [i]!=(uint8)(〜bufi [i])`也可以做到这一点,而不是使用temp变量。 (8认同)

Lun*_*din 22

问题是整数提升。该~操作是非常危险的!

如果为~bufi[i],则~根据整数提升来提升的操作数。使代码等同于~(int)bufi[i]

因此,在第二种情况下,buf[i] != (~bufi[i])您得到类似的内容0xXX != 0xFFFFFFFFYY,其中“ XX”和“ YY”是您要比较的实际值,而0xFFFF是通过对a进行按位补码而放置的意外内容int。这将始终取值为,true以便编译器可以优化部分代码,从而创建一个非常细微的错误。

如果tmp = ~bufi[i];您通过截断0xFFFFFFFFYY为“ YY”(您感兴趣的值)来避开此错误。

有关详细信息,请参见隐式类型提升规则。还可以考虑采用MISRA-C来躲避此类细微的错误。


chq*_*lie 5

正如Lundin和dbush所指出的,第二个版本中的比较总是失败,因为uint8提升为的任何值的相反值int都不同于所有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;
}
Run Code Online (Sandbox Code Playgroud)

可以看出在Godbolt的编译器的探险家,都gccclang彻底检测,并优化代码出来:

verifyInverseBuffer:
    test    edx, edx
    sete    al
    ret
Run Code Online (Sandbox Code Playgroud)

gcc 产生一个相当隐晦的警告,指出可疑的有符号/无符号比较问题,这不是真正的问题...关闭但没有香蕉。

<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
Run Code Online (Sandbox Code Playgroud)