不明白“假设签署溢出”警告(Don't understand “assuming signe

2019-08-01 17:05发布

我正进入(状态:

警告:假设符号溢出假设(X + C)时,不会发生<X总是假[-Wstrict溢出]

在这条线:

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

m_PositionIndexm_EndIndex类型的itk::Index ( http://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;
}

但我没有得到警告那里。 有什么想法,什么是我的演示和实际的代码,或者什么之间的不同可能会导致在真正的代码的警告? 我与海合会双方4.7.0和4.7.2与-Wall标志的警告。

Answer 1:

简单地禁用此警告,使用-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 ++作为一个低级别的“系统语言”,你可能只是想编译器做你告诉它做什么 - 依靠底层硬件的行为。 鉴于标准的语言,我不知道如何在编译器无关的方式实现这一目标。



Answer 2:

编译器告诉你,它有一个片段的足够的静态知识知道,测试将一直成功,如果它可以优化检验,假设没有符号运算溢出。

换句话说,只有这样, x + 1 < x永远不会返回true,当x签订是,如果x已经是最大的带符号值。 [-Wstrict-overflow]让编译器警告时,它可以假设没有签订除会溢出; 它基本上是告诉你这将优化掉的测试,因为符号溢出是未定义的行为。

如果你想取消此警告,摆脱了测试。



Answer 3:

尽管你的问题的年龄,因为你没有改变你的代码的那部分呢,我想这个问题仍然存在,你仍然没有得到什么实际发生在这里的有用expanation,所以让我试试吧:

在C(和C ++),如果增加两个符号整数导致溢出时,整个程序运行的行为是未定义的。 因此,执行程序可以为所欲为的环境(格式化硬盘或启动nuklear战争,假设neccessary硬件是存在的)。

GCC通常既不,但它其他令人讨厌的事情(仍然可能导致一方在不走运的情况下)。 为了证明这一点,让我给你从费利克斯·冯·莱特纳@一个简单的例子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? (WTF,德语“当时泰特费费” - “你会做费费”,费费是费利克斯·冯·莱特纳,我借鉴了代码示例的昵称)。 哦,还有,顺便说一句,当时泰特费费? 右:写在2007年对这个问题的bug报告! 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可以证明它永远不会溢出。 在这种情况下,这是绝对OK删除代码(编译器仍然可以发出警告死代码消除在这里,但DCE发生如此频繁,这可能没人要的警告)。 但是,在你的“真实世界代码”,数据流分析失败,因为并非所有所需的信息存在于它发生。 所以,GCC不知道,是否++溢出。 所以,它警告你,它假设永远不会发生(然后GCC删除整个if语句)。

在这里解决(或隐藏!!!)问题的一种方法是断言的a < INT_MAX之前做a++ 。 总之,我担心,你可能有一些真正的错误,但我将不得不调查更弄明白。 但是,你可以自己看着办吧,是创建MVCE的正确方法:取与警告的源代码,添加任何neccessary从包括文件获取源代码的独立( gcc -E是有点极端,但会做的工作),然后开始删除任何不使警告消失,直到你有一个代码,你不能再删除任何东西。



Answer 4:

我认为,编译器改变

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

为更优化的像

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

所以

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

导致溢出的情况下(感谢赛斯)未定义的行为。



Answer 5:

就我而言,这是由编译器一个很好的警示。 我有一个愚蠢的复制/粘贴错误,我有一个循环内的循环,每个为同一组的条件。 事情是这样的:

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

此警告我节省了时间调试。 谢谢,GCC!



Answer 6:

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

我相信,GCC以某种方式搞清楚什么中的值“这 - > m_PositionIndex [IN]”等被从计算,即,这两个值是从相同的值,一个衍生具有偏移它也可以被证明是正。 因此它的数字,即如果/ else结构的一个分支是无法访问的代码。

这是不好的。 当然。 为什么? 好了,恰好有三种可能性:

  1. 海湾合作委员会是正确的代码是无法访问的,你忽视了这种情况。 在这种情况下,你只是有臃肿的代码。 最好的三种可能性,但在代码质量方面仍然很糟糕。

  2. 海湾合作委员会是正确的代码是无法访问的,但你希望它是可到达。 在这种情况下,你需要采取在为什么它是无法到达第二个外观和修复了你的逻辑。

  3. GCC是错误的,你无意中发现了其中存在一个bug。 不太可能,但可能的,不幸的是很难证明的。

非常糟糕的事情是,你绝对需要可以找出到底为什么代码无法访问,或者您需要反驳说GCC是正确的(这是说起来容易做起来难)。 根据墨菲定律,假设情况下,1或3。没有证明它会确保它是2的情况下,那你将不得不去寻找这个bug第二次...



文章来源: Don't understand “assuming signed overflow” warning