왜이 루프가“경고 : 반복 3u가 정의되지 않은 동작을 불러옵니다”를 생성하고 4 줄 이상을 출력합니까?


162

이것을 컴파일 :

#include <iostream>

int main()
{
    for (int i = 0; i < 4; ++i)
        std::cout << i*1000000000 << std::endl;
}

그리고 gcc다음과 같은 경고를 생성합니다 :

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

부호있는 정수 오버플로가 있음을 이해합니다.

내가 얻을 수없는 것은 왜 i오버플로 작업으로 인해 값이 깨지는 것입니까?

GCC를 사용하여 x86에서 정수 오버플로가 무한 루프를 일으키는 이유에 대한 답변을 읽었습니다 . 그러나 이런 일이 발생 하는지 아직 확실하지 않습니다. "정의되지 않은"은 "모든 일이 발생할 수 있음"을 의미하지만 이 특정 행동 의 근본 원인은 무엇입니까?

온라인 : http://ideone.com/dMrRKR

컴파일러: gcc (4.8)


49
부호있는 정수 오버플로 => 정의되지 않은 동작 => 비강 데몬. 그러나 나는 그 예가 아주 훌륭하다는 것을 인정해야한다.
dyp

1
어셈블리 출력 : goo.gl/TtPmZn
Bryan Chen

1
GCC 4.8에서 O2, 및 O3플래그를 사용하여 발생하지만 그렇지 않은 O0경우O1
Alex

3
비강 데몬을 읽을 때 @dyp 나는 당신이 재미있는 것을 볼 때 약간 코를 호흡하는 "imgur 웃음"을했다. 그리고 나는 깨달았다 ... 나는 비강 데몬에 의해 저주를 받아야한다!
corsiKa

4
이 북마크하기 나는 "기술적으로 UB이다 그러나 무엇을해야 그 다음에 누군가의 증류기를 연결할 수 있도록 일을 :)"
MM

답변:


107

부호있는 정수 오버플로 (엄격히 말해서 "부호없는 정수 오버플로"와 같은 것은 없음)는 정의되지 않은 동작을 의미 합니다. 그리고 이것은 모든 일이 일어날 수 있다는 것을 의미하며, 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) i2보다 큰 값을 가진 모든 반복 에는 정의되지 않은 동작이 있기 때문에 -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


7
나는 아직도 여기서 무슨 일이 일어나고 있는지 이해하지 못한다. 왜 i그 자체가 영향을 받는가? 일반적으로 정의되지 않은 행동에는 이런 종류의 이상한 부작용이 없습니다. 결국 i*100000000rvalue가되어야합니다
vsoftco

26
(1) i2보다 큰 값을 가진 모든 반복 에는 정의되지 않은 동작이 있기 때문에 -2 (2) i <= 2최적화 목적으로-> (3) 루프 조건이 항상 참 이라고 가정 할 수 있습니다. ) 무한 루프로 최적화되었습니다.
TC

28
@vsoftco : 진행중인 것은 강도 감소 ,보다 구체적으로, 유도 변수 제거의 경우 입니다. 컴파일러는 i각 반복마다 1e9 씩 증가 하는 코드를 내보내고 그에 따라 루프 조건을 변경 하여 곱셈을 제거합니다 . 이 프로그램은 잘 동작하는 차이를 관찰 할 수 없으므로 "as as"규칙에 따라 완벽하게 유효한 최적화입니다. 아아, 그렇지 않다. 그리고 최적화는 "누출"된다.
JohannesD

8
@JohannesD는 이것이 깨지는 이유를 파악했습니다. 그러나 루프 종료 조건에 오버플로가 발생하지 않기 때문에 이는 나쁜 최적화입니다. 강도 감소를 사용해도 괜찮습니다. 프로세서의 승수가 (4 * 100000000)으로 (100000000 + 100000000 + 100000000 + 100000000)과 다를 수 있고 "정의되지 않았습니다." "알고있는 사람"이 합리적입니다. 그러나 4 번 실행되고 정의되지 않은 결과를 생성하는 잘 동작하는 루프를 대체하는 것은 "정의되지 않았기 때문에"4 번 이상 실행되는 것으로 대체됩니다. 관용입니다.
Julie

14
@JulieinAustin 그것은 당신에게 바보 일지 모르지만 완벽하게 합법적입니다. 긍정적 인 측면에서 컴파일러는 그것에 대해 경고합니다.
milleniumbug

68

짧은 대답, 특히이 gcc문제를 문서화 했으므로 gcc 4.8 릴리스 노트에서 다음강조합니다 .

GCC는 이제보다 적극적인 분석을 사용하여 언어 표준에 의해 적용된 제약 조건을 사용하여 루프 반복 횟수의 상한을 도출합니다 . 이로 인해 SPEC CPU 2006 464.h264ref 및 416.gamess와 같은 부적합한 프로그램이 더 이상 예상대로 작동하지 않을 수 있습니다. 이 적극적인 분석을 비활성화하기 위해 새로운 옵션 인 -fno-aggressive-loop-optimizations가 추가되었습니다. 일정한 반복 횟수를 알고 있지만 마지막 반복에 도달하기 전에 또는 마지막 반복 중에 루프에서 정의되지 않은 동작이 발생하는 것으로 알려진 일부 루프에서 GCC는 반복 횟수의 하한을 도출하는 대신 루프에서 정의되지 않은 동작에 대해 경고합니다. 루프. -Wno-aggressive-loop-optimizations로 경고를 비활성화 할 수 있습니다.

우리가 실제로 사용한다면 -fno-aggressive-loop-optimizations 무한 루프 동작을 하면 중단해야하며 테스트 한 모든 경우에 적용됩니다.

아는와 긴 대답이 시작 부호있는 정수 오버 플로우가 초안 C ++ 표준 섹션보고에 의해 정의되지 않은 동작입니다 5 표현식4 말한다 :

표현식을 평가하는 동안 결과가 수학적으로 정의되지 않았거나 해당 유형의 표현 가능한 값 범위에없는 경우 동작이 정의되지 않습니다 . [참고 : 대부분의 기존 C ++ 구현에서는 정수 오버플로를 무시합니다. 0으로 나누기, 제로 제수를 사용하여 나머지를 형성하며 모든 부동 소수점 예외는 기계마다 다르며 일반적으로 라이브러리 기능으로 조정할 수 있습니다. — 끝 노트

우리는 표준에 따르면 정의되지 않은 동작은 다음과 같은 정의와 함께 제공되는 메모에서 예측할 수 없다고 말합니다.

[참고 :이 국제 표준이 명시적인 행동 정의를 생략하거나 프로그램이 잘못된 구성 또는 잘못된 데이터를 사용하는 경우 정의되지 않은 동작이 예상 될 수 있습니다. 허용되지 않는 정의 된 동작은 예측할 수없는 결과로 상황을 완전히 무시하는 것부터, 환경의 문서화 된 방식 (진단 메시지 발행 여부에 관계없이)으로 번역 또는 프로그램 실행 중 행동, 번역 또는 실행 종료 (발급 포함)에 이르기까지 다양 합니다. 진단 메시지). 많은 잘못된 프로그램 구성은 정의되지 않은 동작을 유발하지 않습니다. 그들은 진단을 받아야합니다. — 끝 노트]

그러나 gcc옵티마이 저가 이것을 무한 루프로 바꾸기 위해 무엇을 할 수 있을까요? 완전히 엉뚱한 소리. 그러나 고맙게도 gcc우리는 경고에서 그것을 알아낼 수있는 단서를 제공합니다.

warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
   std::cout << i*1000000000 << std::endl;
                  ^

실마리는 Waggressive-loop-optimizations무엇입니까? 다행스럽게도이 최적화가 이런 방식으로 코드를 깨뜨린 것은 이번이 처음이 아니며 John Regehr 가 다음 코드를 보여주는 GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks 기사에 사례를 문서화 했기 때문에 운이 좋았습니다 .

int d[16];

int SATD (void)
{
  int satd = 0, dd, k;
  for (dd=d[k=0]; k<16; dd=d[++k]) {
    satd += (dd < 0 ? -dd : dd);
  }
  return satd;
}

기사는 말합니다 :

정의되지 않은 동작은 루프를 종료하기 직전에 d [16]에 액세스하고 있습니다. C99에서는 배열 끝을지나 한 위치에있는 요소에 대한 포인터를 만드는 것이 합법적이지만 해당 포인터를 역 참조해서는 안됩니다.

그리고 나중에 말합니다 :

자세하게는 다음과 같습니다. d [++ k]를 볼 때 AC 컴파일러는 증가하지 않은 k 값이 배열 범위 내에 있다고 가정 할 수 있습니다. 그렇지 않으면 정의되지 않은 동작이 발생하기 때문입니다. 이 코드의 경우 GCC는 k가 0..15 범위에 있다고 추론 할 수 있습니다. 조금 후에, GCC가 k <16을 볼 때, "Aha – 그 표현은 항상 참이므로 무한 루프가 있습니다." 컴파일러가 잘 정의 된 가정을 사용하여 유용한 데이터 흐름 사실을 유추하는 상황

따라서 어떤 경우 컴파일러가 해야하는 일은 부호있는 정수 오버플로가 정의되지 않은 동작 이므로 i항상 작아야 4하므로 무한 루프가 있어야 한다고 가정 합니다 .

그는 이것이이 코드를 볼 때 악명 높은 Linux 커널 널 포인터 검사 제거 와 매우 유사하다고 설명합니다 .

struct foo *s = ...;
int x = s->f;
if (!s) return ERROR;

gcc널 포인터 s에서 지연된 이후 로 s->f;널 포인터를 역 참조하는 것은 정의되지 않은 동작 s이므로 널이 아니어야하므로 if (!s)다음 행 에서 확인을 최적화 합니다.

여기서의 교훈은 최신 옵티마이 저가 정의되지 않은 동작을 악용하는 데 매우 적극적이며 대부분 더 공격적이라는 것입니다. 분명히 몇 가지 예만 있으면 최적화 프로그램이 프로그래머에게는 완전히 비합리적으로 보이지만 최적화 프로그램의 관점에서 회상하는 것이 합리적이라는 것을 알 수 있습니다.


7
나는 이것이 컴파일러 라이터가하고있는 일임을 이해하지만 (컴파일러와 심지어 옵티 마이저 또는 두 개를 작성하는 데 사용되었지만) "정의되지 않은"데도 "유용한"행동이 있으며 더 공격적인 최적화를 향한 행진이 있습니다 그냥 정신 이상입니다. 위에서 인용 한 구성은 잘못되었지만 오류 확인을 최적화하는 것은 사용자에게 적대적입니다.
Julie

1
@JulieinAustin 개발자가 정의되지 않은 동작을 피해야한다는 것은 실제로 문제의 절반에 불과하다는 놀라운 행동이라고 동의합니다. 컴파일러도 개발자에게 더 나은 피드백을 제공해야합니다. 이 경우 정보가 충분하지 않지만 경고가 생성됩니다.
Shafik Yaghmour 2014 년

3
나는 그것이 좋은 것이라고 생각하고, 더 좋고 더 빠른 코드를 원합니다. UB는 절대 사용해서는 안됩니다.
paulm

1
@paulm morally UB는 분명히 나쁘지만 개발자가 UB 및 기타 문제가 프로덕션 응용 프로그램에 영향을 미치기 전에 잡을 수 있도록 도와주는 clang 정적 분석기 와 같은 더 나은 도구 를 제공한다고 주장하기는 어렵습니다 .
Shafik Yaghmour 2014 년

1
@ShafikYaghmour 또한 개발자가 경고를 무시하는 경우 clang 출력에주의를 기울일 가능성은 무엇입니까? 이 문제는 공격적인 "정의되지 않은 경고 없음"정책으로 쉽게 잡을 수 있습니다. Clang은 권장되지만 필수는 아닙니다.
deworde

24

tl; dr 이 코드는 integer + positive integer == negative integer 테스트를 생성합니다 . 일반적으로 옵티마이 저는이를 최적화하지 않지만 std::endl다음에 사용되는 특정 경우 컴파일러는이 테스트를 최적화합니다. endl아직 특별한 점을 찾지 못했습니다.


-O1 이상의 어셈블리 코드에서 gcc는 루프를 다음과 같이 리팩터링합니다.

i = 0;
do {
    cout << i << endl;
    i += NUMBER;
} 
while (i != NUMBER * 4)

올바르게 작동하는 가장 큰 값은 715827882즉 floor ( INT_MAX/3)입니다. 의 어셈블리 스 니펫 -O1은 다음과 같습니다.

L4:
movsbl  %al, %eax
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
addl    $715827882, %esi
cmpl    $-1431655768, %esi
jne L6
    // fallthrough to "return" code

은 참고가 -1431655768있다 4 * 7158278822의 보수에.

타격 -O2하면 다음과 같이 최적화됩니다.

L4:
movsbl  %al, %eax
addl    $715827882, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655768, %esi
jne L6
leal    -8(%ebp), %esp
jne L6 
   // fallthrough to "return" code

따라서 최적화는 단지 addl위로 올라간 것입니다.

715827883대신에 다시 컴파일 하면 -O1 버전은 변경된 숫자 및 테스트 값과 동일합니다. 그러나 -O2는 다음과 같이 변경합니다.

L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2

에있는 곳 cmpl $-1431655764, %esi에서 -O1해당 줄이 제거되었습니다 -O2. 옵티마이 저는에 추가하는 715827883것이 %esi같을 수 없다고 결정해야합니다.-1431655764 .

이것은 꽤 수수께끼입니다. 에 있음을 추가 INT_MIN+1 하지 최적화 결정해야합니다, 그래서 예상되는 결과를 생성이 %esi될 수 없다INT_MIN+1 하고 그 결정 이유를 잘 모르겠어요.

작업 예 715827882에서 숫자에 더하는 것이 같을 수 없다고 결론을 내리는 것도 똑같이 유효합니다 INT_MIN + 715827882 - 2! (이것은 랩 어라운드가 실제로 발생하는 경우에만 가능하지만)이 예제에서 라인 아웃을 최적화하지는 않습니다.


내가 사용한 코드는 다음과 같습니다.

#include <iostream>
#include <cstdio>

int main()
{
    for (int i = 0; i < 4; ++i)
    {
        //volatile int j = i*715827883;
        volatile int j = i*715827882;
        printf("%d\n", j);

        std::endl(std::cout);
    }
}

(가) 경우 std::endl(std::cout)다음 제거 최적화가 더 이상 발생하지 않습니다. 실제로이를 대체하면 인라인 된 std::cout.put('\n'); std::flush(std::cout);경우에도 최적화가 발생하지 않습니다 std::endl.

인라인은 std::endl루프 구조의 초기 부분에 영향을 미치는 것 같습니다 (어떻게하고 있는지 이해하지 못하지만 다른 사람이 할 수 있도록 여기에 게시 할 것입니다).

원래 코드와 -O2:

L2:
movl    %esi, 28(%esp)
movl    28(%esp), %eax
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    __ZSt4cout, %eax
movl    -12(%eax), %eax
movl    __ZSt4cout+124(%eax), %ebx
testl   %ebx, %ebx
je  L10
cmpb    $0, 28(%ebx)
je  L3
movzbl  39(%ebx), %eax
L4:
movsbl  %al, %eax
addl    $715827883, %esi
movl    %eax, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    %eax, (%esp)
call    __ZNSo5flushEv
jmp L2                  // no test

의 mymanual 인라인으로 std::endl, -O2:

L3:
movl    %ebx, 28(%esp)
movl    28(%esp), %eax
addl    $715827883, %ebx
movl    $LC0, (%esp)
movl    %eax, 4(%esp)
call    _printf
movl    $10, 4(%esp)
movl    $__ZSt4cout, (%esp)
call    __ZNSo3putEc
movl    $__ZSt4cout, (%esp)
call    __ZNSo5flushEv
cmpl    $-1431655764, %ebx
jne L3
xorl    %eax, %eax

이 두 가지 차이점 중 하나 %esi는 원본과 %ebx두 번째 버전에서 사용 된다는 것 입니다 . 일반적으로 %esi그리고 %ebx일반적으로 정의 된 의미에 차이가 있습니까? (x86 어셈블리에 대해서는 잘 모릅니다).


옵티마이 저의 논리가 무엇인지 정확하게 알아내는 것이 좋을 것입니다.이 단계에서 왜 어떤 경우에는 테스트가 최적화되어 있고 어떤 것은 그렇지 않은지 명확하지 않습니다.
MM

8

gcc에서보고되는이 오류의 또 다른 예는 일정한 반복 횟수로 실행되는 루프가 있지만 카운터 변수를 해당 항목 수보다 적은 수의 배열에 대한 인덱스로 사용하는 경우입니다.

int a[50], x;

for( i=0; i < 1000; i++) x = a[i];

컴파일러는이 루프가 어레이 'a'외부의 메모리에 액세스하려고 시도 할 것을 결정할 수 있습니다. 컴파일러는 다소 비밀스러운 메시지로 이에 대해 불평합니다.

반복 xxu는 정의되지 않은 동작을 호출합니다 [-Werror = aggressive-loop-optimizations]


더 비밀스러운 것은 최적화가 켜져있을 때만 메시지가 발생한다는 것입니다. M $ VB 메시지 "Array out of bound"는 인형 용입니까?
Ravi Ganesh

6

내가 얻을 수없는 것은 왜 오버플로 작업으로 인해 가치가 깨지는 것입니까?

정수 오버플로가 4 번째 반복 ( i = 3) 에서 발생하는 것으로 보입니다 . signed정수 오버플로는 정의되지 않은 동작을 호출합니다 . 이 경우 아무것도 예측할 수 없습니다. 루프는 반복 만 할 수 있습니다4 몇 번만 되거나 무한대로 또는 다른 것으로 갈 수 있습니다!
결과는 컴파일러마다 다르거 나 동일한 컴파일러의 다른 버전에 따라 달라질 수 있습니다.

C11 : 1.3.24 정의되지 않은 동작 :

이 국제 표준이 요구 사항을 부과하지 않는
행동 [참고 :이 국제 표준이 명시적인 행동 정의를 생략하거나 프로그램이 잘못된 구성 또는 잘못된 데이터를 사용하는 경우 정의되지 않은 동작이 예상 될 수 있습니다. 허용되지 않는 정의 된 동작은 예측할 수없는 결과로 상황을 완전히 무시하는 것부터, 환경의 특성화 된 문서화 된 방식으로 진단 또는 프로그램 실행 중 (진단 메시지 발행 여부에 관계없이), 번역 또는 실행 종료 (발급 포함)에 이르기까지 다양합니다. 진단 메시지) . 많은 잘못된 프로그램 구성은 정의되지 않은 동작을 유발하지 않습니다. 그들은 진단을 받아야합니다. — 끝 노트]


@bits_international; 예.
haccks

4
당신이 옳아 요, 내가 왜 downvoted했는지 설명하는 것이 공정합니다. 이 답변의 정보는 정확하지만, 교육하지 않고 완전히 방에 코끼리를 무시 : 파손이 분명히 발생하는 다른 장소에서 오버 플로우를 일으키는 동작을보다 (정지 상태). 이 특정 상황에서 문제가 발생하는 방식에 대해서는 설명하지 않습니다. 비록 이것이이 질문의 핵심이지만 말입니다. 교사의 답변이 문제의 핵심을 해결할뿐만 아니라 추가 질문을하지 않는 일반적인 나쁜 교사 상황입니다. 그것은 거의 같은 소리 ...
Szabolcs

5
"이것은 정의되지 않은 행동이며,이 시점에서 그것이 어떻게 또는 왜 깨지는지는 신경 쓰지 않는다. 표준은 그것을 깨뜨릴 수있다. 더 이상의 질문은 없다." 그런 의미는 아니었지만 그런 것 같습니다. 나는 SO에 대해 (불행하게도 일반적인) 태도 중 적은 것을보고 싶습니다. 이것은 실제로 유용하지 않습니다. 사용자 입력을 얻는 경우 표준 에 따라 프로그램의 다른 부분으로 인해 폭발 할 수 있다고 말하더라도 모든 단일 부호있는 정수 연산 후에 오버플로를 확인하는 것이 합리적이지 않습니다 . 이해 하는 방법 은 중단하는 것은 하지 실제로이 같은 도움 피할 문제.
Szabolcs 2016 년

2
@Szabolcs : C를 두 가지 언어로 생각하는 것이 가장 좋을 것입니다. 하나는 간단한 컴파일러가 의도 한 대상 플랫폼에서는 신뢰할 수 있지만 생성하지 않는 구문을 사용하는 프로그래머의 도움으로 합리적으로 효율적인 실행 코드를 얻을 수 있도록 설계되었습니다. 다른 사람들은 표준위원회에 의해 무시되었고, 프로그래머가해야 할 수도 있고 그렇지 않을 수도있는 추가 최적화를 컴파일러가 적용 할 수 있도록하기 위해 표준이 지원을 요구하지 않는 그러한 모든 구성을 배제하는 제 2 언어 포기
supercat

1
@Szabolcs " 사용자 입력을 받으면 모든 단일 부호있는 정수 연산 후에 오버플로를 확인하는 것이 합리적이지 않습니다. "-너무 늦었 기 때문에 정확합니다. 모든 단일 부호있는 정수 연산 전에 오버 플로우를 확인해야 합니다.
melpomene
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.