问题 gcc优化?错误?以及它对项目的实际意义


我的问题分为三个部分

问题1
考虑下面的代码,

#include <iostream>
using namespace std;

int main( int argc, char *argv[])
{

    const int v = 50;
    int i = 0X7FFFFFFF;

    cout<<(i + v)<<endl;

    if ( i + v < i )
    {
        cout<<"Number is negative"<<endl;
    }
    else
    {
        cout<<"Number is positive"<<endl;
    }

    return 0;
}

没有使用特定的编译器优化选项或使用O的标志。它是基本的编译命令g ++ -o test main.cpp用于构成可执行文件。

看似非常简单的代码,在SUSE 64位操作系统,gcc版本4.1.2中有奇怪的行为。预期输出为“Number is negative”,而仅在SUSE 64位OS中,输出为“Number is positive”。

经过一些分析并对代码进行“解除”后,我发现编译器以下面的格式进行优化 -

  • 由于i在比较的两边是相同的,所以不能在同一表达式中更改,从等式中删除“i”。
  • 现在,比较导致了 if ( v < 0 )其中v是常数正数,因此在编译期间,else部分cout函数地址被添加到寄存器中。没有找到cmp / jmp指令。

我看到行为仅在gcc 4.1.2 SUSE 10中。在AIX 5.1 / 5.3和HP IA64中尝试时,结果如预期。

以上优化是否有效?
或者,使用int的溢出机制不是一个有效的用例?

问题2
现在当我更改条件语句时 if (i + v < i) 至 if ( (i + v) < i ) 即使这样,行为也是一样的,至少我个人不同意,因为提供了额外的括号,我希望编译器创建一个临时的内置类型变量并进行比较,从而使优化无效。

问题3
假设我有一个庞大的代码库,我迁移了我的编译器版本,这样的bug /优化可能会对我的系统行为造成严重破坏。从业务角度来看,仅仅因为编译器升级而再次测试所有代码行是非常无效的。

我认为,出于所有实际目的,这些类型的错误很难捕获(在升级期间),并且总是会泄漏到生产现场。

任何人都可以建议任何可能的方法来确保这些错误/优化对我现有的系统/代码库没有任何影响吗? 


PS:

  • 当从代码中删除const for v时,编译器不会进行优化。
  • 我相信,使用溢出机制来查找变量是否来自MAX - 50值(在我的情况下)是完全没问题的。

更新(1)
我想要实现什么?变量我将是一个计数器(一种syncID)。如果我执行离线操作(50操作)然后在启动期间,我想重置我的计数器,为此我检查边界值(重置它)而不是盲目地添加它。

我不确定我是否依赖于硬件实现。我知道0X7FFFFFFF是最大正值。我所做的只是通过为此增加价值,我期望返回值为负值。我不认为这个逻辑与硬件实现有任何关系。

无论如何,谢谢你的意见。


更新(2)
大多数inpit声明我依赖于溢出检查的较低级别行为。我有一个相同的问题,

  • 如果是这种情况,对于unsigned int,如何在下溢或溢出期间验证和重置该值?比如如果v = 10,i = 0X7FFFFFFE,我想重置i = 9.同样对于下溢?

除非我检查数字的否定性,否则我无法做到这一点。所以我的主张是,当一个值加到+ MAX_INT时,int必须返回一个负数。

请让我知道您的意见。


2697
2018-05-23 16:18


起源

不,这不好,因为这是未定义的行为。你假设int是2的补码中的32位数并且MAX_INT +必须是负的,这是错误的。 - Ingo
也许你可以告诉我们你真正想要做什么,这样我们就可以找到符合标准的方法。 - Keith Randall
很好的例子,为什么你不能说,“我知道我的处理器确实绕过有符号的溢出,因此它是安全的”。 - Steve Jessop
20年前人们坚持认为0x7fff是最大的正值...在阳光下真的没什么新东西。 :) - Ingo
@Ingo:是的,但这不是问题所在:有问题的实现确实有32位 int,INT_MAX确实等于0X7FFFFFFF。问题是程序溢出了带有未定义行为的有符号整数类型。在这种情况下,由于优化,行为是它得到比较“错误”。 - Steve Jessop


答案:


这是一个已知问题,我不认为它被认为是编译器中的错误。当我用gcc 4.5编译时 -Wall -O2 它警告说

警告:假设假设(X + c)<X始终为假,则不会发生签名溢出

虽然你的代码  溢出。

你可以通过 -fno-strict-overflow 标志以关闭该特定优化。


11
2018-05-23 16:27



+1。需要强调的是,GCC的行为是符合标准的,因为标准认为此类操作中的溢出会导致未定义的行为。 - Johannes Schaub - litb
“假设有签名溢出” - 的确如此。按规定的标准,(0x7fffffff + 50)必须溢出? - Ingo
该标准不需要0x7fffffff + 50溢出。但是,它允许它溢出,如果是,则标准允许未定义的行为。 - Keith Randall
-fno-strict-overflow在所有gcc版本中都不存在。无论如何,这是我得到的最佳答案和选择。另外,我认为我对INT_MAX + x的假设是否定的是有效的(我不依赖于机器实现)。请让我知道你对此的看法。 - kumar_m_kiran
@ kumar-m-kiran:你的假设是无效的。 C ++标准说有符号整数溢出是未定义的行为(5/5和3.9.1 / 4,其中声明 无符号 类型环绕。通过省略签名类型不需要这样做)。你在Java中说的是真的,但在C ++中却没有。 - Steve Jessop


您的代码会产生未定义的行为。 C和C ++语言对于有符号整数运算没有“溢出机制”。您的计算溢出了有符号整数 - 行为立即未定义。考虑到它形成“编译器中的错误与否”,尝试分析它的位置也没有什么不同 i = i++ + ++i 例子。

GCC编译器具有基于C / C ++语言规范部分的优化。它被称为“严格溢出语义”或湖泊。它基于以下事实:在C ++中向有符号整数添加正值总是会产生更大的值或导致未定义的行为。这立即意味着编译器可以完全自由地假设总和总是更大。该优化的一般性质与GCC中也存在的“严格别名”优化非常相似。他们都引起了一些来自海湾合作委员会用户界更“黑客”部分的抱怨,其中许多人甚至不怀疑他们在C / C ++计划中依赖的技巧只是非法黑客攻击。


3
2018-05-23 17:56





Q1:也许,64位实现中的数字确实是正数?谁知道?在调试代码之前,我只需要printf(“%d”,i + v);

Q2:括号只是告诉编译器如何解析表达式。这通常以树的形式完成,因此优化器根本看不到任何括号。并且可以自由地转换表达式。

问题3:这就是为什么,作为c / c ++程序员,你不能编写承担底层硬件特定属性的代码,例如,int是32位数量的二进制补码形式。


2
2018-05-23 16:31



大多数(所有?)64位机器仍将“int”定义为32位。因此,如果增加值,则值为0x7fffffff的int仍会溢出。 - avl_sweden


这条线是什么:

cout<<(i + v)<<endl;

SUSE示例中的输出?你确定你没有64位整数?


0
2018-05-23 16:27



它告诉我,“bug”发生在64位实现上。 - Ingo
你有一个64位整数的系统的例子吗?我认为这是非常罕见的。 - avl_sweden


好的,所以这差不多六年了,问题得到了回答。我仍然觉得有些内容并没有让我满意,所以我添加了一些评论,希望这对本次讨论的未来读者有所帮助。 (比如当我搜索它时我自己。)

  1. 使用gcc 4.1.2指定的OP没有任何特殊标志。我假设缺席了 -O 国旗相当于 -O0。在没有请求优化的情况下,为什么gcc会以报告的方式优化代码?在我看来,这似乎是一个编译器错误。我还假设这已在以后的版本中得到修复(例如,一个答案提及gcc 4.5和 -fno-strict-overflow 优化标志)。当前的gcc手册页指出了这一点 -fstrict-overflow 包括在内 -O2 或者更多。

  2. 在当前版本的gcc中,有一个选项 -fwrapv 这使您可以使用导致OP出现问题的代码。当然,您必须确保知道整数类型的位大小。来自gcc手册页:

-fstrict溢出
.....
另请参见-fwrapv选项。使用-fwrapv表示整数有符号溢出
完全定义:它包装。 ...使用-fwrapv某些类型的溢出
允许的。例如,如果编译器在进行算术运算时出现溢出
在常量上,溢出的值仍然可以与-fwrapv一起使用,但不能与其他情况一起使用。

0
2018-03-04 15:41