带有内置函数的奇怪 GCC6 优化
Strange GCC6 optimization with builtin function
使用 GCC6 和下面的代码片段,这个测试
if (i > 31 || i < 0) {
为false,执行此printf
printf("i > 31 || i < 0 is FALSE, where i=%d", i);
并产生这个非常奇怪的输出(GCC6):
i > 31 || i < 0 is FALSE, where i=32 /* weird result with GCC6 !!! */
而使用 GCC4 我得到:
i > 31 || i < 0 is true, where i=32 /* result Ok with GCC4
*/
看起来还不错。
怎么会这样??
代码片段(损坏的遗留代码!):
static int check_params(... input parameters ...) {
/* Note that value can be 0 (zero) */
uint32_t value = ....
int i;
i = __builtin_ctz(value);
if (i > 31 || i < 0) {
printf("i > 31 || i < 0 is true, where i=%d", i);
/* No 1 found */
return 0;
} else {
printf("i > 31 || i < 0 is FALSE, where i=%d", i);
}
return i;
}
根据 the documentation about GCC builtin functions,必须避免调用 __builtin_ctz(0):
Built-in Function: int __builtin_ctz (unsigned int x) Returns the
number of trailing 0-bits in x, starting at the least significant bit
position. If x is 0, the result is undefined.
很明显,解决编码错误的方法是在调用 __builtin_ctz(value) 之前简单地检查值。这是清楚和理解的。
我可以停在那里并转到其他主题...但是,我仍然不明白我怎么可能(使用损坏的代码)获得以下输出:
i > 31 || i < 0 is FALSE, where i=32 /* weird result with GCC6 !!! */
奇怪的 GCC6 优化之类的?
以防万一:
Cross-compiler: arm-linux-gcc
Architecture: -march=armv7-a
有什么想法吗?
除非有未定义的行为,__builtin_ctz
总是 return 0 到 31 之间的数字,GCC 知道这一点。因此,检查 i > 31 || i < 0
将始终为假(再次禁止未定义的行为)并且可以被优化掉。
如果您查看生成的程序集,您会发现该条件根本没有出现在代码中(then-case 也没有出现)。
未定义的行为并不意味着 "the value will be arbitrary." 它意味着编译器可以做到 literally anything it wants to。在这种情况下,看起来编译器能够静态验证 只要 value
不是 0,i
将始终介于 0 和 31 之间(含) .所以它甚至不会为 then 子句生成代码。
你真幸运,恶魔没有从你的鼻子里跑出来。
另请参阅:Undefined behavior can result in time travel, The premature downcast, Why undefined behavior may call a never-called function, and many many many other discussions of UB 此处。
编译器假定未定义行为不会发生。它可以做出这样的假设,因为如果约束被违反并且行为未定义,任何结果都是可能的,包括由错误假设导致的结果。
如果没有Undefined Behaviour,则i
不能为负数或大于31。在此基础上,if
语句中的条件可以在编译时进行优化。
无法预测 printf
实际打印的值,因此它实际上调用 printf
时 i
恰好是什么。在这种情况下,它恰好是 32,但它可以是任何东西。
C 标准指出,实现将未定义行为视为邀请编译器按照环境的文档化时尚特征行事是很常见的,基本原理指出将行为分类为 UB 旨在导致当市场需要时,质量实现提供超出标准强制要求的功能。尽管如此,从他们的行为来看,一些编译器供应商认为,让编译器寻找聪明的方法来利用自由地处理某些情况的自由比让他们在实际情况下采用理智的约束行为更有价值(例如 __builtin_ctz() 在做 CPU 所做的任何事情或产生一些任意值之间以未指定的方式进行选择。
我个人相当怀疑任何 "optimizations" 的价值,如果允许编译器做除了产生一个值或执行 CPU 操作之外的任何自然结果之外的任何事情,将会促进接近允许程序员假设在 CPU 本身不会做任何奇怪的事情的平台上的价值,该操作将简单地产生一个没有副作用的未指定结果。尽管如此,gcc 的作者似乎认为要求程序员添加额外的源代码来处理无需额外机器代码即可处理的情况会以某种方式改进 "efficiency".
使用 GCC6 和下面的代码片段,这个测试
if (i > 31 || i < 0) {
为false,执行此printf
printf("i > 31 || i < 0 is FALSE, where i=%d", i);
并产生这个非常奇怪的输出(GCC6):
i > 31 || i < 0 is FALSE, where i=32 /* weird result with GCC6 !!! */
而使用 GCC4 我得到:
i > 31 || i < 0 is true, where i=32 /* result Ok with GCC4 */
看起来还不错。
怎么会这样??
代码片段(损坏的遗留代码!):
static int check_params(... input parameters ...) {
/* Note that value can be 0 (zero) */
uint32_t value = ....
int i;
i = __builtin_ctz(value);
if (i > 31 || i < 0) {
printf("i > 31 || i < 0 is true, where i=%d", i);
/* No 1 found */
return 0;
} else {
printf("i > 31 || i < 0 is FALSE, where i=%d", i);
}
return i;
}
根据 the documentation about GCC builtin functions,必须避免调用 __builtin_ctz(0):
Built-in Function: int __builtin_ctz (unsigned int x) Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined.
很明显,解决编码错误的方法是在调用 __builtin_ctz(value) 之前简单地检查值。这是清楚和理解的。
我可以停在那里并转到其他主题...但是,我仍然不明白我怎么可能(使用损坏的代码)获得以下输出:
i > 31 || i < 0 is FALSE, where i=32 /* weird result with GCC6 !!! */
奇怪的 GCC6 优化之类的?
以防万一:
Cross-compiler: arm-linux-gcc
Architecture: -march=armv7-a
有什么想法吗?
除非有未定义的行为,__builtin_ctz
总是 return 0 到 31 之间的数字,GCC 知道这一点。因此,检查 i > 31 || i < 0
将始终为假(再次禁止未定义的行为)并且可以被优化掉。
如果您查看生成的程序集,您会发现该条件根本没有出现在代码中(then-case 也没有出现)。
未定义的行为并不意味着 "the value will be arbitrary." 它意味着编译器可以做到 literally anything it wants to。在这种情况下,看起来编译器能够静态验证 只要 value
不是 0,i
将始终介于 0 和 31 之间(含) .所以它甚至不会为 then 子句生成代码。
你真幸运,恶魔没有从你的鼻子里跑出来。
另请参阅:Undefined behavior can result in time travel, The premature downcast, Why undefined behavior may call a never-called function, and many many many other discussions of UB 此处。
编译器假定未定义行为不会发生。它可以做出这样的假设,因为如果约束被违反并且行为未定义,任何结果都是可能的,包括由错误假设导致的结果。
如果没有Undefined Behaviour,则i
不能为负数或大于31。在此基础上,if
语句中的条件可以在编译时进行优化。
无法预测 printf
实际打印的值,因此它实际上调用 printf
时 i
恰好是什么。在这种情况下,它恰好是 32,但它可以是任何东西。
C 标准指出,实现将未定义行为视为邀请编译器按照环境的文档化时尚特征行事是很常见的,基本原理指出将行为分类为 UB 旨在导致当市场需要时,质量实现提供超出标准强制要求的功能。尽管如此,从他们的行为来看,一些编译器供应商认为,让编译器寻找聪明的方法来利用自由地处理某些情况的自由比让他们在实际情况下采用理智的约束行为更有价值(例如 __builtin_ctz() 在做 CPU 所做的任何事情或产生一些任意值之间以未指定的方式进行选择。
我个人相当怀疑任何 "optimizations" 的价值,如果允许编译器做除了产生一个值或执行 CPU 操作之外的任何自然结果之外的任何事情,将会促进接近允许程序员假设在 CPU 本身不会做任何奇怪的事情的平台上的价值,该操作将简单地产生一个没有副作用的未指定结果。尽管如此,gcc 的作者似乎认为要求程序员添加额外的源代码来处理无需额外机器代码即可处理的情况会以某种方式改进 "efficiency".