Dav*_*ria 22 c++ gcc gcc-warning
我正进入(状态:
警告:假设假设(X + c)<X始终为假,则不会发生签名溢出[-Wstrict-overflow]
在这条线上:
if ( this->m_PositionIndex[in] < this->m_EndIndex[in] )
Run Code Online (Sandbox Code Playgroud)
m_PositionIndex和m_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 for context)
谁能解释一下这会引起什么警告?我没有看到(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;
}
Run Code Online (Sandbox Code Playgroud)
但我没有收到警告.关于我的演示和真实代码之间有什么不同,或者什么可能导致实际代码中的警告?我用-Wall标志用gcc 4.7.0和4.7.2发出警告.
nob*_*bar 15
要简单地禁用此警告,请使用-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
Run Code Online (Sandbox Code Playgroud)
在我的情况下,警告以某种方式与展开for循环的优化器相关联,因此我能够通过声明来解决volatile int c=0.修复它的另一件事是声明unsigned int c=0,但我不确定为什么会产生影响.修复它的另一件事是使循环计数足够大以至于循环不会展开,但这不是一个有用的解决方案.
那么这个警告到底在说什么呢?要么说优化器已经修改了算法的语义(假设没有溢出),要么只是告诉你优化器假设你的代码没有溢出有符号整数的未定义行为.除非溢出有符号整数是程序预期行为的一部分,否则此消息可能并不表示代码中存在问题 - 因此您可能希望将其禁用为正常构建.如果您收到此警告但不确定相关代码,则最安全的方法是禁用优化-fwrapv.
顺便说一句,我在GCC 4.7上遇到了这个问题,但是相同的代码在没有使用4.8的情况下进行了编译 - 也许表明GCC开发人员认识到在正常情况下需要稍微"严格"(或者可能是只是由于优化器的差异).
哲学...
在[C++ 11:N3242 $ 5.0.4]中,它声明......
如果在评估表达式期间,结果未在数学上定义或未在其类型的可表示值范围内,则行为未定义.
这意味着符合标准的编译器可以简单地假设从不发生溢出(即使对于无符号类型).
在C++中,由于模板和复制构造函数等功能,无意义操作的省略是一项重要的优化器功能.但有时候,如果您使用C++作为低级"系统语言",您可能只希望编译器按照您的要求执行操作 - 并依赖底层硬件的行为.鉴于标准的语言,我不知道如何以独立于编译器的方式实现这一点.
编译器告诉你它有足够的静态知识,知道如果测试可以优化测试,假设没有签名操作会溢出,那么测试将始终成功.
换句话说,x + 1 < x当x签名时,唯一的方法将返回true,如果x已经是最大签名值.[-Wstrict-overflow]让编译器在可以假设没有符号加法会溢出时发出警告; 它基本上告诉你它会优化测试,因为签名溢出是未定义的行为.
如果你想抑制警告,摆脱测试.
尽管你的问题已经存在很久了,但由于你还没有更改那部分代码,我认为问题仍然存在,并且你仍然没有得到关于这里实际发生的情况的有用解释,所以让我试试:
\n在 C(和 C++)中,如果添加两个有符号整数导致溢出,则整个程序运行的行为是未定义的。因此,执行程序的环境可以做任何它想做的事情(格式化硬盘或发动核战争,假设存在必要的硬件)。
\ngcc 通常两者都不做,但它会做其他令人讨厌的事情(在不幸的情况下仍然可能导致其中任何一个)。为了证明这一点,让我给你举一个来自 Felix von Leitner @ http://ptrace.fefe.de/int.c的简单例子:
\n#include <assert.h>\n#include <stdio.h>\n\nint foo(int a) {\n assert(a+100 > a);\n printf("%d %d\\n",a+100,a);\n return a;\n}\n\nint main() {\n foo(100);\n foo(0x7fffffff);\n}\nRun Code Online (Sandbox Code Playgroud)\n\n\n注意:我添加了 stdio.h,以消除有关 printf 未声明的警告。
\n
现在,如果我们运行这个,我们会期望代码在第二次调用 foo 时断言,因为它会创建一个整数溢出并检查它。那么,让我们这样做:
\n$ gcc -O3 int.c -o int && ./int\n200 100\n-2147483549 2147483647\nRun Code Online (Sandbox Code Playgroud)\n搞什么?(WTF,德语“Was t\xc3\xa4te Fefe” - “Fefe 会做什么”,Fefe 是 Felix von Leitner 的昵称,我借用了代码示例)。哦,顺便说一句,是 t\xc3\xa4te Fefe 吗?右:在 2007 年写了一份关于这个问题的错误报告!https://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475
\n回到你的问题。如果您现在尝试深入挖掘,您可以创建一个程序集输出(-S)并进行调查以找出已assert完全删除
$ gcc -S -O3 int.c -o int.s && cat int.s\n[...]\nfoo:\n pushq %rbx // save "callee-save" register %rbx\n leal 100(%rdi), %edx // calc a+100 -> %rdx for printf\n leaq .LC0(%rip), %rsi // "%d %d\\n" for printf\n movl %edi, %ebx // save `a` to %rbx\n movl %edi, %ecx // move `a` to %rcx for printf\n xorl %eax, %eax // more prep for printf\n movl $1, %edi // and even more prep\n call __printf_chk@PLT // printf call\n movl %ebx, %eax // restore `a` to %rax as return value\n popq %rbx // recover "callee-save" %rbx\n ret // and return\nRun Code Online (Sandbox Code Playgroud)\n这里任何地方都没有断言。
\n现在,让我们在编译期间打开警告。
\n$ gcc -Wall -O3 int.c -o int.s\nint.c: In function \'foo\':\nint.c:5:2: warning: assuming signed overflow does not occur when assuming that (X + c) >= X is always true [-Wstrict-overflow]\n assert(a+100 > a);\n ^~~~~~\nRun Code Online (Sandbox Code Playgroud)\n所以,这条消息实际上说的是:a + 100可能会溢出导致未定义的行为。因为你是一位技术精湛的专业软件开发人员,从不做任何错事,我(gcc)确信,这a + 100不会溢出。因为我知道,我也知道,这a + 100 > a永远是真的。因为我知道这一点,所以我知道该断言永远不会触发。因为我知道这一点,所以我可以消除“死代码消除”优化中的整个断言。
这正是 gcc 在这里所做的(并警告您)。
\n现在,在您的小示例中,数据流分析可以确定该整数实际上没有溢出。因此,gcc 不需要假设它永远不会溢出,相反,gcc 可以证明它永远不会溢出。在这种情况下,删除代码是绝对可以的(编译器仍然可以警告此处的死代码消除,但 dce 发生得如此频繁,可能没有人想要这些警告)。但在您的“现实世界代码”中,数据流分析会失败,因为并非存在进行分析所需的所有必要信息。所以,gcc 不知道是否a++溢出。所以它警告你,它假设永远不会发生(然后 gcc 删除整个 if 语句)。
解决(或隐藏!!!)这里问题的一种方法是在a < INT_MAX执行a++. 不管怎样,我担心,你可能有一些真正的错误,但我必须进行更多调查才能弄清楚。但是,您可以自己弄清楚,以正确的方式创建 MVCE:获取带有警告的源代码,从包含文件中添加任何必要的内容以获得独立的源代码(gcc -E有点极端,但可以完成工作),然后开始删除任何不会使警告消失的内容,直到您有了无法再删除任何内容的代码为止。
我认为编译器改变了
positionIndex[i]++;
if ( positionIndex[i] < endIndex[i] )
Run Code Online (Sandbox Code Playgroud)
变成更优化的东西,比如
if ( positionIndex[i]++ < endIndex[i] ) <-- see my comment below, this statement is wrong.
Run Code Online (Sandbox Code Playgroud)
所以
if ( positionIndex[i] + 1 < endIndex[i] )
Run Code Online (Sandbox Code Playgroud)
在溢出的情况下会导致未定义的行为(感谢 Seth)。