부호있는 정수 오버플로 (엄격히 말해서 "부호없는 정수 오버플로"와 같은 것은 없음)는 정의되지 않은 동작을 의미 합니다. 그리고 이것은 모든 일이 일어날 수 있다는 것을 의미하며, C ++의 규칙에 따라 왜 그런 일이 발생하지 않는지 논의하는 것은 의미가 없습니다.
C ++ 11 초안 N3337 : §5.4 : 1
표현식을 평가하는 동안 결과가 수학적으로 정의되지 않았거나 해당 유형의 표현 가능한 값 범위에없는 경우 동작이 정의되지 않습니다. [참고 : 대부분의 기존 C ++ 구현에서는 정수 오버플로를 무시합니다. 0으로 나누기, 제로 제수를 사용하여 나머지를 형성하며 모든 부동 소수점 예외는 기계마다 다르며 일반적으로 라이브러리 기능으로 조정할 수 있습니다. — 끝 참고]
코드로 컴파일 된 코드는 g++ -O3
경고 를 내 보냅니다.-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)
^
프로그램이 수행하는 작업을 분석 할 수있는 유일한 방법은 생성 된 어셈블리 코드를 읽는 것입니다.
전체 어셈블리 목록은 다음과 같습니다.
.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
간신히 어셈블리를 읽을 수는 있지만 addl $1000000000, %edi
줄을 볼 수도 있습니다. 결과 코드는 다음과 같습니다
for(int i = 0; /* nothing, that is - infinite loop */; i += 1000000000)
std::cout << i << std::endl;
@TC의 의견 :
(1) i
2보다 큰 값을 가진 모든 반복 에는 정의되지 않은 동작이 있기 때문에 -2 (2) i <= 2
최적화 목적으로-> (3) 루프 조건이 항상 참 이라고 가정 할 수 있습니다. ) 무한 루프로 최적화되었습니다.
OP 코드의 어셈블리 코드를 정의되지 않은 동작없이 다음 코드의 어셈블리 코드와 비교하는 아이디어를 얻었습니다.
#include <iostream>
int main()
{
// changed the termination condition
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 // here it is
jne L7
lea esp, [ebp-16]
xor eax, eax
pop ecx
; ...snip...
세상에, 그것은 명확하지 않다! 공평하지 않다! 나는 불의 재판을 요구한다!
그것으로 처리, 당신은 버그가있는 코드를 작성하고 기분이 좋지 않습니다. 결과를 참는다.
... 또는 대안으로 더 나은 진단 및 더 나은 디버깅 도구를 적절하게 사용하십시오.
모든 경고를 활성화
-Wall
잘못된 경고없이 모든 유용한 경고를 활성화하는 gcc 옵션입니다. 이것은 항상 사용해야하는 최소한의 것입니다.
- gcc에는 다른 많은 경고 옵션 이 있지만,
-Wall
오 탐지에 대해 경고 할 수 있으므로 활성화되어 있지 않습니다.
- 불행히도 Visual C ++는 유용한 경고를 제공 할 수있는 능력이 뒤떨어져 있습니다. 최소한 IDE는 기본적으로 일부를 활성화합니다.
디버깅을 위해 디버그 플래그 사용
- 정수 오버플로의 경우 오버플로
-ftrapv
에서 프로그램을 트랩합니다.
- 연타 컴파일러는이를위한 우수 :
-fcatch-undefined-behavior
정의되지 않은 동작의 인스턴스의 많은을 잡는다 (참고 : "a lot of" != "all of them"
)
나는 내일 배송이 필요한 프로그램이 아닌 스파게티 엉망이있다! 도와주세요 !!!!!!
gcc 사용 -fwrapv
이 옵션은 덧셈, 뺄셈 및 곱셈의 부호있는 산술 오버플로가 2의 보수 표현을 사용한다고 가정하도록 컴파일러에 지시합니다.
1- 이 규칙은 §3.9.1.4에서 말하는 "부호없는 정수 오버플로"에는 적용되지 않습니다
부호없는 것으로 선언 된 부호없는 정수는 산술 모듈로 2n 의 법칙을 준수해야합니다. 여기서 n은 특정 크기의 정수 값을 나타내는 비트 수입니다.
예를 들어, 결과 UINT_MAX + 1
는 수학적으로 정의됩니다-산술 모듈로의 규칙 2 n