g++ 严格的溢出、优化和警告

3

在使用严格溢出标志进行编译时,以下代码在第二个测试中告诉我,r可能不是我所认为的那样:

    int32_t r(my_rand());
    if(r < 0) {
        r = -r;
        if(r < 0) {   // <-- error on this line
            r = 0;
        }
    }

错误信息为:
/build/buildd/libqtcassandra-0.5.5/tests/cassandra_value.cpp:
     In function 'int main(int, char**)':
/build/buildd/libqtcassandra-0.5.5/tests/cassandra_value.cpp:2341:13:
     error: assuming signed overflow does not occur when simplifying
     conditional to constant [-Werror=strict-overflow]
         if(r < 0) {
         ^

我不理解的是:为什么错误不会在前一行生成?因为实际上,溢出是发生在这里,对吗?
    r = -r;

注意:在gcc 8中,-fstrict-overflow似乎已经被大大简化。它成为了-fwrapv和-fwrapv-pointer的别名。 - MarcH
2个回答

4

编辑:我删除了我的第一个答案,因为它是无效的。这里是完全新的版本。感谢@Neil Kirk指出我的错误。

问题的答案在这里:https://stackoverflow.com/a/18521660/2468549

GCC始终假定有符号溢出永远不会发生,并基于此进行优化,因此它(始终)优化掉内部的if (r < 0)块。

如果您打开-Wstrict-overflow,那么编译器会发现,在r = -r之后r < 0仍可能为真(如果r == -2 ^ 31最初),这会导致错误(错误是由基于溢出永远不会发生的假设进行的优化引起的,而不是溢出可能性本身 - 这是-Wstrict-overflow的工作原理)。


r=-2147483648 时,-r 是什么? - Neil Kirk
有符号整数的范围是不对称的:从-2147483648到2147483647。因此,将-2147483648与0进行比较(在CPU/汇编语言级别上实现为减法,0-(-2147483648),并检查符号标志)会导致溢出。 - Frax
你是对的。我正在重新编写我的答案。感谢您指出这一点,它最终带领我找到了正确的答案。 - Frax
在某种程度上很有趣,因为我想要捕捉那个特定的情况“并修复它”(在我的情况下是使用0代替)。但那更清晰。 - Alexis Wilke
似乎你必须在实际溢出发生之前修复它 - 因为编译器认为它不会发生,即使它确实会发生。无论看起来多么愚蠢。 - Frax
显示剩余2条评论

0

关于此类溢出的一些补充:

  1. 你可以通过在代码块或函数周围使用pragma来关闭这些警告:

     #pragma GCC diagnostic push
     #pragma GCC diagnostic ignored "-Wstrict-overflow"
     ...offensive code here...
     #pragma GCC diagnostic pop
    

    当然,这意味着你完全忽略了错误并让编译器进行其优化。最好进行测试,确保边缘情况按预期工作!

  2. 使用无符号整数。

    文档中说:

    对于C(和C++),这意味着使用有符号数字进行算术运算时的溢出未定义,这意味着编译器可能会假定它不会发生。

    换句话说,如果算术运算使用无符号整数,则该优化不适用。因此,如果您需要执行类似r = -r这样的操作,则可以先将r复制到相同大小的无符号整数中,然后进行符号更改:

     std::int32_t r = ...;
    
     std::uint32_t q = r;
     q = -q;
    
     if(static_cast<std::int32_t>(q) < 0) ...
    

    这应该按预期工作:if()语句不会被优化掉。

  3. 关闭实际优化。

    注意:这似乎在旧编译器中有效,但当前不能按函数基础上进行。您必须在命令行上使用该命令行选项而不是作为属性。我暂时保留这个,因为它对您可能有用。

     T __attribute__((optimize("-fno-strict-overflow"))) func(...)
     {
         ...
     }
    

    该属性通过生成预期代码来取消了-fstrict-overflow¹错误。现在我的测试在Debug和Release中都通过了。

注意:这是特定于g++的。如果有,查看您的编译器文档以获取等效内容。
无论如何,如Frax所提到的,在-O3模式下,编译器希望通过删除测试和整个代码块来优化测试。整个代码块可以被删除,因为如果它为负,则在r = -r;之后,预计r将为正数。因此,再次测试负数被优化掉了,这就是编译器警告我们的原因。但是,使用-fstrict-overflow属性,您可以要求编译器在该函数中执行该优化。因此,在所有情况下(包括溢出),您都会获得预期的行为。
¹我发现该选项名称令人困惑。在这种情况下,如果您使用-fstrict-overflow,则即使严格的溢出未被遵守,您也要求优化程序执行优化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接