C++ 位not运算的编译器优化

C++ 位not运算的编译器优化,c++,c,embedded,iar,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 =

我有一个简单的函数测试,如果两个数组彼此相反。 除了一个
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;  
}
//这工作正常
uint8验证反向缓冲(uint8*buf、uint8*bufi、uint32 len)
{
uint8 tmp;
对于(uint32 i=0;i

第一个版本的代码有效,第二个版本无效。有人知道为什么吗?或者通过一些测试来探索问题所在?

您看到的是整数升级规则的结果。只要在表达式中使用小于
int
的变量,该值就会提升为
int
类型

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

如果将
~
的结果赋回类型为
uint8\u t
的变量,则值
0xffff00
将转换为
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!=0xFFFFFFYY
,其中“XX”和“YY”是您希望比较的实际值,0xFFFF是通过取
int
的位补码而放置在那里的意外垃圾。这将始终计算为
true
,因此编译器可能会优化掉部分代码,从而产生一个非常微妙的bug

如果
tmp=~bufi[i]
通过将
0xFFFFFFFFYY
截断为您感兴趣的值“YY”,可以避开此错误


有关详细信息,请参阅。也可以考虑使用MISRA-C来规避这样的微妙错误。

正如Lundin和D布什所指出的,第二个版本的比较总是失败,因为任何<代码> UINT8//COD>值提升到int >代码>不同于所有<代码> UINT8/COD>值。换句话说,第二个版本相当于:

//这不符合预期(我只删除了tmp!)
uint8验证反向缓冲(uint8*buf、uint8*bufi、uint32 len){
如果(len)返回0;
返回1;
}
从上可以看到,
gcc
clang
都检测到了这一点,并完全优化了代码:

verifyInverseBuffer:
    test    edx, edx
    sete    al
    ret
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
:在函数“verifyInverseBuffer”中:
:8:16:警告:将无符号值的提升位补码与无符号[-Wsign compare]进行比较
8 |如果(buf[i]!=(~bufi[i]))
|                ^~
编译器返回:0

输入是什么?buf=[0xC0,0xA8,0x03,0x87]bufi=[0x3F,0x57,0xFC,0x78]如果有人对此进行了验证,则验证问题为隐式类型升级。通过在按位操作后强制转换到指定类型或存储在相同类型的temp变量中进行求解。@SupAl如果有答案回答了您的问题,您应该这样做。这个问题的标题很差:您对正在发生的事情做出了(不正确)的假设,然后询问了该假设-(X-Y问题)。你的问题应该是“为什么这不起作用?”。你最好解释一下它是如何工作的——结果和生成结果的输入(uint8)(~bufi[i])
也会这样做,而不是使用temp变量。不幸的是,编写MISRA-C的前提似乎是,该标准将采用与C89最终使用的不同的整数提升规则。例如,
expr>(u16a-u16b)
将被接受的表达式范围只有在该表达式等效于
expr>(uint16_t)(u16a-u16b)
时才真正有意义,当然它不是。通过使用单独的“包装无符号代数环”和“无符号数值”类型为类型系统增加一点复杂性,可以使用真正可移植的……16位类型编写代码(当对“16位无符号数值”进行运算时,两个16位无符号代数环值之间的差值将是同一类型的另一个值,而与
int
的大小无关。)类型的行为就好像它被提升为能够处理整个范围的有符号类型一样——同样,与
int
的大小无关