为什么这个循环产生“警告:迭代3u调用未定义的行为”并输出4行以上? [英] Why does this loop produce "warning: iteration 3u invokes undefined behavior" and output more than 4 lines?
问题描述
正在编译:
#include< iostream>
int main()
{
for(int i = 0; i <4; ++ i)
std :: cout< i * 1000000000 < std :: endl;
}
和 gcc
以下警告:
警告:iteration 3u调用未定义的行为[-Waggressive-循环优化]
std :: cout < i * 1000000000 < std :: endl;
^
我知道有一个有符号整数溢出。
我不能得到的是为什么 i
值被溢出操作破坏了。
我阅读了的答案为什么x86上的整数溢出与GCC导致无限循环?,但我仍然不清楚为什么这发生 - 我得到的未定义意味着任何可能发生但是此特定行为的根本原因是什么?
编译器: gcc(4.8)
签名的整数溢出(严格来说,没有无符号整数溢出这样的东西)意味着。这意味着任何可能发生,并讨论为什么它发生在C ++的规则下是没有意义的。
C ++ 11草案N3337:§5.4: sup> 1
如果在表达式求值过程中,结果不是数学定义的, b $ b它的类型的可表示的值,行为是未定义的。 [注意:大多数现有的C ++实现
忽略整数溢出。处理除以零,使用零除数形成余数,并且所有
浮点异常在机器中不同,并且通常可以通过库函数调整。 -end note]
您的代码编译为 g ++ -O3
即使没有 -Wall
)
a.cpp: main()':
a.cpp:11:18:warning:iteration 3u调用未定义的行为[-Waggressive-loop-optimizations]
std :: cout< i * 1000000000 < std :: endl;
^
a.cpp:9:2:note:包含循环
for(int i = 0; i <4; ++ i)
^
我们可以分析程序正在做什么的唯一方法是读取生成的汇编代码。
这是完整的装配列表:
.filea.cpp
.section .text $ _ZNKSt5ctypeIcE8do_widenEc,x
.linkonce discard
.align 2
LCOLDB0:
LHOTB0:
.align 2
。 p2align 4,,15
.globl __ZNKSt5ctypeIcE8do_widenEc
.def __ZNKSt5ctypeIcE8do_widenEc; .scl 2; .type 32; .endef
__ZNKSt5ctypeIcE8do_widenEc:
LFB860:
.cfi_startproc
movzbl 4(%esp),%eax
ret $ 4
.cfi_endproc
LFE860 :
LCOLDE0:
LHOTE0:
.section .text.unlikely,x
LCOLDB1:
.text
LHOTB1:
。 p2align 4,,15
.def ___tcf_0; .sc13; .type 32; .endef
___tcf_0:
LFB1091:
.cfi_startproc
movl $ __ ZStL8__ioinit,%ecx
jmp __ZNSt8ios_base4InitD1Ev
.cfi_endproc
LFE1091:
.section .text.unlikely,x
LCOLDE1:
.text
LHOTE1:
.def ___main; .sc12; .type 32; .endef
.section .text.unlikely,x
LCOLDB2:
.section .text.startup,x
LHOTB2:
.p2align 4, ,15
.globl _main
.def _main; .scl 2; type 32; .endef
_main:
LFB1084:
.cfi_startproc
leal 4(%esp),%ecx
.cfi_def_cfa 1,0
andl $ -16 ,%esp
pushl -4(%ecx)
pushl%ebp
.cfi_escape 0x10,0x5,0x2,0x75,0
movl%esp,%ebp
pushl%edi
pushl%esi
pushl%ebx
pushl%ecx
.cfi_escape 0xf,0x3,0x75,0x70,0x6
.cfi_escape 0x10,0x7,0x2 ,0x75,0x7c
.cfi_escape 0x10,0x6,0x2,0x75,0x78
.cfi_escape 0x10,0x3,0x2,0x75,0x74
xorl%edi,%edi
subl $ 24 ,%esp
call ___main
L4:
movl%edi,(%esp)
movl $ __ ZSt4cout,%ecx
call __ZNSolsEi
movl%eax ,%esi
movl(%eax),%eax
subl $ 4,%esp
movl -12(%eax),%eax
movl 124(%esi,%eax ),%ebx
testl%ebx,%ebx
je L15
cmpb $ 0,28(%ebx)
je L5
movsbl 39(%ebx) eax
L6:
movl%esi,%ecx
movl%eax,(%esp)
addl $ 1000000000,%edi
call __ZNSo3putEc
subl $ 4 ,%esp
movl%eax,%ecx
call __ZNSo5flushEv
jmp L4
.p2align 4,and 10
L5:
movl%ebx,% ecx
call __ZNKSt5ctypeIcE13_M_widen_initEv
movl(%ebx),%eax
movl 24(%eax),%edx
movl $ 10,%eax
cmpl $ __ ZNKSt5ctypeIcE8do_widenEc, edx
je L6
movl $ 10,(%esp)
movl%ebx,%ecx
call *%edx
movsbl%al,%eax
pushl%edx
jmp L6
L15:
call __ZSt16__throw_bad_castv
.cfi_endproc
LFE1084:
.section .text.unlikely,x
LCOLDE2:
.section .text.startup,x
LHOTE2:
.section .text.unlikely,x
LCOLDB3:
.section。 text.startup,x
LHOTB3:
.p2align 4,and 15
.def __GLOBAL__sub_I_main; .sc13; .type 32; .endef
__GLOBAL__sub_I_main:
LFB1092:
.cfi_startproc
subl $ 28,%esp
.cfi_def_cfa_offset 32
movl $ __ ZStL8__ioinit,%ecx
调用__ZNSt8ios_base4InitC1Ev
movl $ ___ tcf_0,(%esp)
调用_atexit
addl $ 28,%esp
.cfi_def_cfa_offset 4
ret
.cfi_endproc
LFE1092:
.section .text.unlikely,x
LCOLDE3:
.section .text.startup,x
LHOTE3:
.section。 ctors,w
.align 4
.long __GLOBAL__sub_I_main
.lcomm __ZStL8__ioinit,1,1
.identGCC:(i686-posix-dwarf-rev1, MinGW-W64 project)4.9.0
.def __ZNSt8ios_base4InitD1Ev; .scl 2; .type 32; .endef
.def __ZNSolsEi; .scl 2; .type 32; .endef
.def __ZNSo3putEc; .scl 2; .type 32; .endef
.def __ZNSo5flushEv; .scl 2; .type 32; .endef
.def __ZNKSt5ctypeIcE13_M_widen_initEv; .scl 2; .type 32; .endef
.def __ZSt16__throw_bad_castv; .scl 2; .type 32; .endef
.def __ZNSt8ios_base4InitC1Ev; .scl 2; .type 32; .endef
.def _atexit; .scl 2; .type 32; .endef
我几乎不能读取程序集,但是我可以看到 addl $ 1000000000,%edi
线。
生成的代码看起来更像
for(int i = 0; / *没什么, * /; i + = 1000000000)
std :: cout< i<< std :: endl;
@TC的此评论:
<
我怀疑它是这样的:(1)因为每个迭代 i
任何大于2的值都有未定义的行为 - >可以假设 i <= 2
用于优化目的 - >(3)循环条件总是真 - >(4)它被优化为无限循环。 p>
让我想法将OP的代码的汇编代码与下面代码的汇编代码进行比较,没有未定义的行为。
#include< iostream>
int main()
{
//改变终止条件
for(int i = 0; i <3; ++ i)
std :: cout<< i * 1000000000 < std :: endl;
}
事实上,正确的代码有终止条件。
; ... snip ...
L6:
mov ecx,edi
mov DWORD PTR [esp],eax
add esi,1000000000
call __ZNSo3putEc
sub esp,4
mov ecx,eax
call __ZNSo5flushEv
cmp esi,-1294967296 //这里是
jne L7
lea esp,[ebp-16 ]
xor eax,eax
pop ecx
; ... snb ... o
OMG,完全不明显!这不公平!
处理它,你写了buggy代码,你应该感到不舒服。承担后果。
...或者,正确使用更好的诊断和更好的调试工具 - 这是他们的目的:
-
启用所有警告
-
Wall
是gcc选项,启用所有有用的警告,没有误报。这是您应该总是使用的最低限度。 - gcc有许多其他警告选项,但是,它们没有启用
-Wall
,因为他们可能对误报警告 -
- Visual C ++不幸的是落后于提供有用警告的能力。
-
使用调试标志进行调试
- 用于整数溢出
-ftrapv
在溢出时陷阱程序, - Clang编译器优秀的:
-fcatch-undefined-behavior
捕获了大量未定义行为的实例(注意:很多!=所有他们
)
- 用于整数溢出
p>我有一个意大利面条的程序不是由我写的,需要明天发货!帮助!!!!!! 111oneone
使用gcc的
-fwrapv
p>
此选项指示编译器假定加法,减法和乘法的有符号算术溢出使用二进制补码表示。
1 - 此规则不适用于无符号整数溢出,如§3.9.1.4所述
无符号整数,声明为无符号,应遵守算术模2的算法 n 其中n是数字
在整数的特定大小的值表示中的位。
UINT_MAX + 1
的结果在数学上通过算术模2定义 nCompiling this:
#include <iostream> int main() { for (int i = 0; i < 4; ++i) std::cout << i*1000000000 << std::endl; }
and
gcc
produces the following warning:warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations] std::cout << i*1000000000 << std::endl; ^
I understand there is a signed integer overflow.
What I cannot get is why
i
value is broken by that overflow operation?I've read the answers to Why does integer overflow on x86 with GCC cause an infinite loop?, but I'm still not clear on why this happens - I get that "undefined" means "anything can happen", but what's the underlying cause of this specific behavior?
Online: http://ideone.com/dMrRKR
Compiler:
gcc (4.8)
解决方案Signed integer overflow (as strictly speaking, there is no such thing as "unsigned integer overflow") means undefined behaviour. And this means anything can happen, and discussing why does it happen under the rules of C++ doesn't make sense.
C++11 draft N3337: §5.4:1
If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined. [ Note: most existing implementations of C++ ignore integer overflows. Treatment of division by zero, forming a remainder using a zero divisor, and all floating point exceptions vary among machines, and is usually adjustable by a library function. —end note ]
Your code compiled with
g++ -O3
emits warning (even without-Wall
)a.cpp: In function 'int main()': a.cpp:11:18: warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations] std::cout << i*1000000000 << std::endl; ^ a.cpp:9:2: note: containing loop for (int i = 0; i < 4; ++i) ^
The only way we can analyze what the program is doing, is by reading the generated assembly code.
Here is the full assembly listing:
.file "a.cpp" .section .text$_ZNKSt5ctypeIcE8do_widenEc,"x" .linkonce discard .align 2 LCOLDB0: LHOTB0: .align 2 .p2align 4,,15 .globl __ZNKSt5ctypeIcE8do_widenEc .def __ZNKSt5ctypeIcE8do_widenEc; .scl 2; .type 32; .endef __ZNKSt5ctypeIcE8do_widenEc: LFB860: .cfi_startproc movzbl 4(%esp), %eax ret $4 .cfi_endproc LFE860: LCOLDE0: LHOTE0: .section .text.unlikely,"x" LCOLDB1: .text LHOTB1: .p2align 4,,15 .def ___tcf_0; .scl 3; .type 32; .endef ___tcf_0: LFB1091: .cfi_startproc movl $__ZStL8__ioinit, %ecx jmp __ZNSt8ios_base4InitD1Ev .cfi_endproc LFE1091: .section .text.unlikely,"x" LCOLDE1: .text LHOTE1: .def ___main; .scl 2; .type 32; .endef .section .text.unlikely,"x" LCOLDB2: .section .text.startup,"x" LHOTB2: .p2align 4,,15 .globl _main .def _main; .scl 2; .type 32; .endef _main: LFB1084: .cfi_startproc leal 4(%esp), %ecx .cfi_def_cfa 1, 0 andl $-16, %esp pushl -4(%ecx) pushl %ebp .cfi_escape 0x10,0x5,0x2,0x75,0 movl %esp, %ebp pushl %edi pushl %esi pushl %ebx pushl %ecx .cfi_escape 0xf,0x3,0x75,0x70,0x6 .cfi_escape 0x10,0x7,0x2,0x75,0x7c .cfi_escape 0x10,0x6,0x2,0x75,0x78 .cfi_escape 0x10,0x3,0x2,0x75,0x74 xorl %edi, %edi subl $24, %esp call ___main L4: movl %edi, (%esp) movl $__ZSt4cout, %ecx call __ZNSolsEi movl %eax, %esi movl (%eax), %eax subl $4, %esp movl -12(%eax), %eax movl 124(%esi,%eax), %ebx testl %ebx, %ebx je L15 cmpb $0, 28(%ebx) je L5 movsbl 39(%ebx), %eax L6: movl %esi, %ecx movl %eax, (%esp) addl $1000000000, %edi call __ZNSo3putEc subl $4, %esp movl %eax, %ecx call __ZNSo5flushEv jmp L4 .p2align 4,,10 L5: movl %ebx, %ecx call __ZNKSt5ctypeIcE13_M_widen_initEv movl (%ebx), %eax movl 24(%eax), %edx movl $10, %eax cmpl $__ZNKSt5ctypeIcE8do_widenEc, %edx je L6 movl $10, (%esp) movl %ebx, %ecx call *%edx movsbl %al, %eax pushl %edx jmp L6 L15: call __ZSt16__throw_bad_castv .cfi_endproc LFE1084: .section .text.unlikely,"x" LCOLDE2: .section .text.startup,"x" LHOTE2: .section .text.unlikely,"x" LCOLDB3: .section .text.startup,"x" LHOTB3: .p2align 4,,15 .def __GLOBAL__sub_I_main; .scl 3; .type 32; .endef __GLOBAL__sub_I_main: LFB1092: .cfi_startproc subl $28, %esp .cfi_def_cfa_offset 32 movl $__ZStL8__ioinit, %ecx call __ZNSt8ios_base4InitC1Ev movl $___tcf_0, (%esp) call _atexit addl $28, %esp .cfi_def_cfa_offset 4 ret .cfi_endproc LFE1092: .section .text.unlikely,"x" LCOLDE3: .section .text.startup,"x" LHOTE3: .section .ctors,"w" .align 4 .long __GLOBAL__sub_I_main .lcomm __ZStL8__ioinit,1,1 .ident "GCC: (i686-posix-dwarf-rev1, Built by MinGW-W64 project) 4.9.0" .def __ZNSt8ios_base4InitD1Ev; .scl 2; .type 32; .endef .def __ZNSolsEi; .scl 2; .type 32; .endef .def __ZNSo3putEc; .scl 2; .type 32; .endef .def __ZNSo5flushEv; .scl 2; .type 32; .endef .def __ZNKSt5ctypeIcE13_M_widen_initEv; .scl 2; .type 32; .endef .def __ZSt16__throw_bad_castv; .scl 2; .type 32; .endef .def __ZNSt8ios_base4InitC1Ev; .scl 2; .type 32; .endef .def _atexit; .scl 2; .type 32; .endef
I can barely even read assembly, but even I can see the
addl $1000000000, %edi
line. The resulting code looks more likefor(int i = 0; /* nothing, that is - infinite loop */; i += 1000000000) std::cout << i << std::endl;
This comment of @T.C.:
I suspect that it's something like: (1) because every iteration with
i
of any value larger than 2 has undefined behavior -> (2) we can assume thati <= 2
for optimization purposes -> (3) the loop condition is always true -> (4) it's optimized away into an infinite loop.gave me idea to compare the assembly code of the OP's code to the assembly code of the following code, with no undefined behaviour.
#include <iostream> int main() { // changed the termination condition for (int i = 0; i < 3; ++i) std::cout << i*1000000000 << std::endl; }
And, in fact, the correct code has termination condition.
; ...snip... L6: mov ecx, edi mov DWORD PTR [esp], eax add esi, 1000000000 call __ZNSo3putEc sub esp, 4 mov ecx, eax call __ZNSo5flushEv cmp esi, -1294967296 // here it is jne L7 lea esp, [ebp-16] xor eax, eax pop ecx ; ...snip...
OMG, that's completely not obvious! It's not fair! I demand trial by fire!
Deal with it, you wrote the buggy code and you should feel bad. Bear the consequences.
...or, alternatively, make proper use of better diagnostics and better debugging tools - that's what they are for:
enable all warnings
-Wall
is the gcc option that enables all useful warnings with no false positives. This is a bare minimum that you should always use.- gcc has many other warning options, however, they are not enabled with
-Wall
as they may warn on false positives - Visual C++ unfortunately is lagging behind with the ability to give useful warnings. At least the IDE enables some by default.
use debug flags for debugging
- for integer overflow
-ftrapv
traps the program on overflow, - Clang compiler is excellent for this:
-fcatch-undefined-behavior
catches a lot of instances of undefined behaviour (note:"a lot of" != "all of them"
)
- for integer overflow
I have a spaghetti mess of a program not written by me that needs to be shipped tomorrow! HELP!!!!!!111oneone
Use gcc's
-fwrapv
This option instructs the compiler to assume that signed arithmetic overflow of addition, subtraction and multiplication wraps around using twos-complement representation.
1 - this rule does not apply to "unsigned integer overflow", as §3.9.1.4 says that
Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2n where n is the number of bits in the value representation of that particular size of integer.
and e.g. result of
UINT_MAX + 1
is mathematically defined - by the rules of arithmetic modulo 2n这篇关于为什么这个循环产生“警告:迭代3u调用未定义的行为”并输出4行以上?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
-