使用GCC6和下面的代码片段,此测试
if (i > 31 || i < 0) {
为假,并且此 printf 被执行
printf("i > 31 || i < 0 is FALSE, where i=%d", i);
并产生这个非常奇怪的输出(GCC6(:
i> 31|| i <0 是FALSE,其中i=32/* GCC6 !!的奇怪结果 */
而使用 GCC4 我得到:
i> 31|| i <0 为真,其中i=32/* 结果 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;
}
根据有关 GCC 内置函数的文档,必须避免调用 __builtin_ctz(0(:
内置函数:int__builtin_ctz(无符号 int x(返回 x 中尾随 0 位的数,从最低有效位开始 位置。如果 x 为 0,则结果未定义。
所以很明显,编码错误的解决方案是在调用__builtin_ctz(value(之前简单地检查值。这是清楚和理解的。
我可以停在那里,转到其他话题......但是,我仍然不明白我怎么可能(使用损坏的代码(获得以下输出:
i> 31|| i <0 是FALSE,其中i=32/* GCC6 !!的奇怪结果 */
奇怪的GCC6优化还是什么?
以防万一:
Cross-compiler: arm-linux-gcc
Architecture: -march=armv7-a
知道吗?
除非未定义的行为,否则__builtin_ctz
将始终返回一个介于 0 和 31 之间的数字,GCC 知道这一点。因此,检查i > 31 || i < 0
将始终为假(再次禁止未定义的行为(,并且可以优化。
如果您查看生成的程序集,您会发现该条件根本没有出现在代码中(当时的情况也没有(。
未定义的行为并不意味着"值将是任意的"。这意味着编译器几乎可以做任何它想做的事情。在这种情况下,编译器似乎能够静态验证,只要value
不为0,i
将始终介于 0 和 31 之间(包括 0 和 31(。因此,它甚至懒得为 then-子句生成代码。
你只是幸运,恶魔没有从你的鼻子里出来。
另请参阅:未定义的行为会导致时间旅行,过早的下落,为什么未定义的行为可以调用从未调用的函数,以及这里许多其他关于UB的讨论。
编译器假定未定义的行为不会发生。它可以做出这种假设,因为如果违反了约束并且行为未定义,则任何结果都是可能的,包括错误假设导致的结果。
如果没有未定义的行为,则i
不能为负数或大于 31。在此基础上,可以在编译时优化if
语句中的条件。
printf
实际打印的值无法预测,因此它实际上调用了printf
,无论i
碰巧是什么。在这种情况下,它恰好是 32,但它可能是任何东西。
C 标准指出,实现通常会将未定义行为视为邀请编译器以记录的环境特征的方式行事,并且基本原理指出,将行为分类为 UB 旨在导致高质量的实现,在市场需求时提供超出标准规定的功能。 尽管如此,从他们的行为来看,一些编译器供应商认为,让编译器寻找聪明的方法来利用自由来荒谬地处理某些情况比让他们在实际操作时采取理智约束的行为更有价值(例如,让__builtin_ctz((以未指定的方式选择做CPU所做的任何事情或产生一些任意值(。
我个人宁愿怀疑,通过允许编译器做任何事情来促进任何"优化"的价值,而不是产生一个值或执行CPU操作,无论结果如何自然,都会接近允许程序员假设在CPU本身不做任何奇怪事情的平台上, 该操作将仅产生一个没有副作用的未指定结果。 尽管如此,gcc的作者似乎认为,要求程序员添加额外的源代码来处理不需要额外机器代码就可以处理的情况,会以某种方式提高"效率"。