24

我正进入(状态:

警告:假设 (X + c) < X 始终为假 [-Wstrict-overflow] 时,不会发生有符号溢出

在这条线上:

if ( this->m_PositionIndex[in] < this->m_EndIndex[in] ) 

m_PositionIndexm_EndIndex类型itk::Indexhttp://www.itk.org/Doxygen/html/classitk_1_1Index.html),它们operator[]返回一个signed long.

(这里是第 37 行:https ://github.com/Kitware/ITK/blob/master/Modules/Core/Common/include/itkImageRegionConstIteratorWithIndex.hxx用于上下文)

谁能在这里解释什么会导致该警告?我在任何地方都看不到这种模式(x+c) < x——因为这只是一个signed long比较。

我试图在一个独立的例子中重现它:

#include <iostream>

namespace itk
{
  struct Index
  {
    signed long data[2];

    Index()
    {
      data[0] = 0;
      data[1] = 0;
    }

    signed long& operator[](unsigned int i)
    {
      return data[i];
    }
  };
}
int main (int argc, char *argv[])
{
  itk::Index positionIndex;
  itk::Index endIndex;

  for(unsigned int i = 0; i < 2; i++)
  {
    positionIndex[i]++;
    if ( positionIndex[i] < endIndex[i] )
    {
      std::cout << "something" << std::endl;
    }
  }

  return 0;
}

但我没有得到警告。关于我的演示和真实代码之间有什么不同的想法,或者是什么可能导致真实代码中的警告?我收到带有 -Wall 标志的 gcc 4.7.0 和 4.7.2 的警告。

4

6 回答 6

16

要简单地禁用此警告,请使用-Wno-strict-overflow. 要改为禁用触发此警告的特定优化,请使用-fno-strict-overflow-fwrapv

gcc 手册页描述了可以使用级别控制此警告:-Wstrict-overflow=n

如果这是由于 导致您的构建停止-Werror,您可以在不隐藏警告的情况下通过使用-Wno-error=strict-overflow(或只是-Wno-error覆盖-Werror)来解决问题。


分析和评论...

我收到了同样的警告,并花了几个小时试图在一个较小的示例中重现它,但从未成功。我的真实代码涉及在模板类中调用内联函数,但算法简化为以下...

int X = some_unpredictable_value_well_within_the_range_of_int();
for ( int c=0; c<4; c++ ) assert( X+c >= X ); ## true unless (X+c) overflows

在我的情况下,警告在某种程度上与展开for循环的优化器相关,因此我能够通过声明volatile int c=0. 修复它的另一件事是声明unsigned int c=0,但我不确定为什么会有所作为。修复它的另一件事是使循环计数足够大以至于循环不会被展开,但这不是一个有用的解决方案。

那么这个警告到底在说什么?要么是说优化器修改了你的算法的语义(假设没有溢出),要么只是告诉你优化器假设你的代码没有溢出有符号整数的未定义行为。除非溢出有符号整数是您程序预期行为的一部分,否则此消息可能并不表示您的代码存在问题——因此您可能希望在正常构建时禁用它。如果您收到此警告但不确定有问题的代码,则使用 禁用优化可能是最安全的-fwrapv

顺便说一句,我在 GCC 4.7 上遇到了这个问题,但是使用 4.8 编译的相同代码没有警告——也许表明 GCC 开发人员认识到在正常情况下需要不那么“严格”(或者可能是只是由于优化器的差异)。


哲学...

在 [C++11: N3242 $5.0.4] 中,它声明...

如果在计算表达式期间,结果未在数学上定义或不在其类型的可表示值范围内,则行为未定义。

这意味着符合标准的编译器可以简单地假设不会发生溢出(即使对于无符号类型)。

在 C++ 中,由于模板和复制构造函数等特性,省略无意义的操作是重要的优化器能力。但是,有时,如果您将 C++ 用作低级“系统语言”,您可能只是希望编译器按照您的要求执行 - 并依赖于底层硬件的行为。鉴于标准的语言,我不确定如何以独立于编译器的方式实现这一点。

于 2013-06-20T04:20:12.157 回答
8

编译器告诉您,它对该片段有足够的静态知识,可以知道如果假设没有签名操作会溢出,它可以优化测试,那么测试将始终成功。

换句话说,唯一在有符号x + 1 < x时返回真的x方法是如果x已经是最大有符号值。[-Wstrict-overflow]让编译器在可以假设没有带符号的加法溢出时发出警告;它基本上是在告诉您它将优化测试,因为有符号溢出是未定义的行为。

如果要抑制警告,请摆脱测试。

于 2013-08-09T20:34:45.047 回答
4

尽管您的问题已经很老了,但由于您尚未更改代码的那部分,我认为问题仍然存在,并且您仍然没有得到关于这里实际发生的事情的有用解释,所以让我尝试一下:

在 C(和 C++)中,如果添加两个有符号整数导致溢出,则整个程序运行的行为是未定义的。因此,执行您的程序的环境可以为所欲为(格式化您的硬盘或开始一场核战争,假设存在必要的硬件)。

gcc 通常两者都不做,但它会做其他令人讨厌的事情(在不幸的情况下仍可能导致其中任何一个)。为了证明这一点,让我给你一个来自 Felix von Leitner @ http://ptrace.fefe.de/int.c的简单例子:

#include <assert.h>
#include <stdio.h>

int foo(int a) {
  assert(a+100 > a);
  printf("%d %d\n",a+100,a);
  return a;
}

int main() {
  foo(100);
  foo(0x7fffffff);
}

注意:我添加了 stdio.h,以消除有关未声明 printf 的警告。

现在,如果我们运行它,我们希望代码在第二次调用 foo 时断言,因为它会创建一个整数溢出并检查它。所以,让我们这样做:

$ gcc -O3 int.c -o int && ./int
200 100
-2147483549 2147483647

怎么回事?(WTF,德语为“Was täte Fefe”-“Fefe 会做什么”,Fefe 是 Felix von Leitner 的昵称,我借用了代码示例)。哦,顺便说一句,是 täte Fefe 吗?正确:在 2007 年就这个问题写一个错误报告!https://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475

回到你的问题。如果您现在尝试深入挖掘,您可以创建一个程序集输出 (-S) 并进行调查以发现assert已完全删除

$ gcc -S -O3 int.c -o int.s && cat int.s
[...]
foo:
        pushq   %rbx             // save "callee-save" register %rbx
        leal    100(%rdi), %edx  // calc a+100 -> %rdx for printf
        leaq    .LC0(%rip), %rsi // "%d %d\n" for printf
        movl    %edi, %ebx       // save `a` to %rbx
        movl    %edi, %ecx       // move `a` to %rcx for printf
        xorl    %eax, %eax       // more prep for printf
        movl    $1, %edi         // and even more prep
        call    __printf_chk@PLT // printf call
        movl    %ebx, %eax       // restore `a` to %rax as return value
        popq    %rbx             // recover "callee-save" %rbx
        ret                      // and return

在任何地方都没有断言。

现在,让我们在编译期间打开警告。

$ gcc -Wall -O3 int.c -o int.s
int.c: In function 'foo':
int.c:5:2: warning: assuming signed overflow does not occur when assuming that (X + c) >= X is always true [-Wstrict-overflow]
  assert(a+100 > a);
  ^~~~~~

所以,这条消息实际上说的是:a + 100可能会溢出导致未定义的行为。因为你是一个技术娴熟的专业软件开发人员,从不做错任何事,我(gcc)肯定知道,这a + 100不会溢出。因为我知道,我也知道,这a + 100 > a总是正确的。因为我知道,我知道断言永远不会触发。因为我知道,我可以在“死代码消除”优化中消除整个断言。

这正是 gcc 在这里所做的(并警告您)。

现在,在您的小示例中,数据流分析可以确定该整数实际上没有溢出。所以,gcc 不需要假设它永远不会溢出,相反,gcc 可以证明它永远不会溢出。在这种情况下,删除代码是绝对可以的(编译器仍然可以在这里警告删除死代码,但是 dce 经常发生,可能没有人想要这些警告)。但是在你的“真实世界代码”中,数据流分析失败了,因为并非所有必要的信息都存在。所以,gcc 不知道是否a++溢出。所以它警告你,它假设永远不会发生(然后 gcc 删除整个 if 语句)。

解决(或隐藏!!!)这里问题的一种方法是a < INT_MAX在执行a++. 无论如何,我担心你可能有一些真正的错误,但我必须进行更多调查才能弄清楚。但是,您可以自己弄清楚,以正确的方式创建您的 MVCE:获取带有警告的源代码,从包含文件中添加任何必要的内容以获取独立的源代码(gcc -E有点极端,但可以完成工作),然后开始删除任何不会使警告消失的内容,直到您有一个无法再删除任何内容的代码。

于 2019-01-14T16:45:09.207 回答
1

就我而言,这是来自编译器的一个很好的警告。我有一个愚蠢的复制/粘贴错误,我在循环中有一个循环,每个循环都是相同的一组条件。像这样的东西:

for (index = 0; index < someLoopLimit; index++)
{
    // blah, blah, blah.....
    for (index = 0; index < someLoopLimit; index++)
    {
        //  More blah, blah, blah....
    }
}

这个警告为我节省了调试时间。谢谢,海湾合作委员会!!!

于 2013-08-09T19:43:22.903 回答
0

我认为编译器改变了

positionIndex[i]++;
if ( positionIndex[i] < endIndex[i] )

变成更优化的东西,比如

if ( positionIndex[i]++ < endIndex[i] )   <-- see my comment below, this statement is wrong.

所以

if ( positionIndex[i] + 1 < endIndex[i] )

在溢出的情况下会导致未定义的行为(感谢 Seth)。

于 2012-10-20T03:03:17.037 回答
0

这几乎可以肯定是一个错误,尽管我不知道它是三个可能的错误中的哪一个。

我相信,gcc 正在以某种方式弄清楚 'this->m_PositionIndex[in]' 等中的值是从什么计算得出的,即两个值都来自相同的值,一个带有偏移量的值也可以证明是正的. 因此,它表明 if/else 构造的一个分支是无法访问的代码。

这是不好的。确实。为什么?嗯,正好有三种可能:

  1. gcc 是正确的,因为代码无法访问,而您忽略了这种情况。在这种情况下,您只是拥有臃肿的代码。三种可能性中最好的,但在代码质量方面仍然很差。

  2. gcc 是正确的,因为代码无法访问,但您希望它可以访问。在这种情况下,您需要再次查看它无法访问的原因并修复您的逻辑中的错误。

  3. gcc 是错误的,您偶然发现了其中的错误。不太可能,但有可能,不幸的是很难证明。

真正糟糕的是,您绝对需要找出代码无法访问的确切原因,或者您需要证明 gcc 是正确的(说起来容易做起来难)。根据墨菲定律,假设情况 1. 或 3. 没有证明它会确保它是情况 2.,并且您将不得不再次寻找错误...

于 2013-06-21T22:19:15.603 回答