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_t
的 buf[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 上看到的那样,gcc
和 clang
都检测到了这一点并完全优化了代码:
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
我有一个简单的函数来测试两个数组是否互为逆数组。
除了 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_t
的 buf[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" 您感兴趣的值来避免此错误。
有关详细信息,请参阅
正如 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 上看到的那样,gcc
和 clang
都检测到了这一点并完全优化了代码:
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