GCC가있는 x86에서 정수 오버플로가 무한 루프를 일으키는 이유는 무엇입니까?

다음 코드는 GCC에서 무한 루프에 들어갑니다.

#include <iostream>
using namespace std;

int main(){
    int i = 0x10000000;

    int c = 0;
    do{
        c++;
        i += i;
        cout << i << endl;
    }while (i > 0);

    cout << c << endl;
    return 0;
}

그래서 여기 거래는 다음과 같습니다 오버 플로우가 기술적으로 정의되지 않은 동작입니다 정수를 체결했다. 그러나 x86의 GCC는 x86 정수 명령어를 사용하여 정수 산술을 구현합니다.

따라서 정의되지 않은 동작이라는 사실에도 불구하고 오버플로를 감쌀 것으로 예상했습니다. 그러나 그것은 사실이 아닙니다. 그래서 내가 무엇을 놓쳤습니까?

나는 이것을 사용하여 이것을 컴파일했다 :

~/Desktop$ g++ main.cpp -O2

GCC 출력 :

~/Desktop$ ./a.out
536870912
1073741824
-2147483648
0
0
0

... (infinite loop)

최적화가 비활성화되면 무한 루프가 없으며 출력이 정확합니다. Visual Studio는이를 올바르게 컴파일하고 다음 결과를 제공합니다.

올바른 출력 :

~/Desktop$ g++ main.cpp
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
3

다른 변형은 다음과 같습니다.

i *= 2;   //  Also fails and goes into infinite loop.
i <<= 1;  //  This seems okay. It does not enter infinite loop.

모든 관련 버전 정보는 다음과 같습니다.

~/Desktop$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ..

...

Thread model: posix
gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4) 
~/Desktop$ 

질문은 GCC의 버그입니까? 아니면 GCC가 정수 산술을 처리하는 방법에 대해 잘못 이해 했습니까?

*이 버그가 C로 재생산된다고 가정하기 때문에이 C에도 태그를 지정하고 있습니다 (아직 확인하지 않았습니다).

편집하다:

다음은 루프 어셈블리입니다. (올바르게 인식 한 경우)

.L5:
addl    %ebp, %ebp
movl    $_ZSt4cout, %edi
movl    %ebp, %esi
.cfi_offset 3, -40
call    _ZNSolsEi
movq    %rax, %rbx
movq    (%rax), %rax
movq    -24(%rax), %rax
movq    240(%rbx,%rax), %r13
testq   %r13, %r13
je  .L10
cmpb    $0, 56(%r13)
je  .L3
movzbl  67(%r13), %eax
.L4:
movsbl  %al, %esi
movq    %rbx, %rdi
addl    $1, %r12d
call    _ZNSo3putEc
movq    %rax, %rdi
call    _ZNSo5flushEv
cmpl    $3, %r12d
jne .L5


답변

표준에 정의되지 않은 동작이라고 표시되면 표준 을 의미합니다 . 무슨 일이든 일어날 수 있습니다. “Anything”에는 “보통 정수가 둘러싸지만 가끔 이상한 일이 발생합니다”가 포함됩니다.

예, x86 CPU에서 정수는 일반적으로 예상대로 줄 바꿈됩니다. 이것은 예외 중 하나입니다. 컴파일러는 정의되지 않은 동작을 유발하지 않으며 루프 테스트를 최적화합니다. 당신이 정말로 랩 어라운드를 원하는 경우, 통과 -fwrapvg++또는 gcc때 컴파일; 이렇게하면 잘 정의 된 (2 보완) 오버 플로우 의미론이 제공되지만 성능이 저하 될 수 있습니다.


답변

간단합니다. 정의되지 않은 동작, 특히 최적화 ( -O2)가 설정된 경우에는 모든 일이 발생할 수 있습니다.

코드는 -O2스위치 없이 예상대로 동작 합니다.

그건 그렇고 icl과 tcc에서는 꽤 잘 작동하지만 그런 것들에 의존 할 수는 없습니다 …

에 따르면 , GCC 최적화는 실제로 오버 플로우 정수 서명 이용한다. 이것은 “버그”가 의도적으로 설계된 것을 의미합니다.


답변

여기서주의해야 할 것은 C ++ 프로그램이 C ++ 추상 머신 (일반적으로 하드웨어 명령어를 통해 에뮬레이트 됨)을 위해 작성되었다는 것입니다. x86 용으로 컴파일 하고 있다는 것은 이것이 정의되지 않은 동작을 가지고 있다는 사실과 는 전혀 관련이 없습니다.

컴파일러는 정의되지 않은 동작의 존재를 사용하여 최적화를 향상시킵니다 (이 예에서와 같이 루프에서 조건부를 제거하여). 기계 코드가 실행될 때 C ++ 추상 기계에 의해 요구되는 결과를 생성해야한다는 요구 사항 외에, C ++ 레벨 구조와 x86 레벨 기계 코드 구조 사이에는 보장되거나 유용한 매핑이 없습니다.


답변

i += i;

// 오버플로가 정의되지 않았습니다.

-fwrapv를 사용하면 맞습니다. -fwrapv


답변

사람들 제발 정의되지 않은 동작이는 것을 정확히 정의되지 . 그것은 무슨 일이든 일어날 수 있다는 것을 의미합니다. 실제로 (이 경우와 같이) 컴파일러는 그렇지 않다고 가정 할 수 있습니다.코드를 더 빠르고 더 작게 만들 수 있다면 원하는대로 행동하십시오. 실행해서는 안되는 코드에서 발생하는 일은 누구나 추측 할 수 있습니다. 주변 코드 (컴파일러에 따라 컴파일러가 다른 코드를 생성 할 수 있음), 사용 된 변수 / 상수, 컴파일러 플래그 등에 따라 달라지며 컴파일러는 업데이트되고 동일한 코드를 다르게 작성할 수 있습니다. 코드 생성에 대한 다른 견해를 가진 다른 컴파일러를 얻으십시오. 또는 다른 기계를 얻으십시오. 동일한 아키텍처 라인의 다른 모델조차도 자체 정의되지 않은 동작을 가질 수 있습니다 (정의되지 않은 opcode를 찾으십시오, 일부 진취적인 프로그래머는 초기 기계 중 일부에서 때로는 유용한 일을한다는 것을 알았습니다 …) . 없습니다 “컴파일러는 정의되지 않은 동작에 대해 명확한 동작을 제공합니다.” 구현 정의 된 영역이 있으며 일관성있게 동작하는 컴파일러를 신뢰할 수 있어야합니다.


답변

컴파일러가 정수 오버플로를 “정의되지 않은”비정의 동작 (부록 L에 정의 된 형태)으로 간주하도록 지정하더라도 정수 오버플로의 결과는보다 구체적인 동작에 대한 특정 플랫폼 약속이없는 경우 최소한 “부분적으로 결정되지 않은 값”으로 간주됩니다. 이러한 규칙에 따라 1073741824 + 1073741824를 추가하면 임의로 2147483648 또는 -2147483648 또는 2147483648 mod 4294967296에 부합하는 다른 값을 산출하는 것으로 간주 될 수 있으며 추가로 얻은 값은 임의로 0 mod 4294967296에 부합하는 값으로 간주 될 수 있습니다.

오버플로가 “부분적으로 결정되지 않은 값”을 생성하도록 허용하는 규칙은 부록 L의 문자와 정신을 준수하기에 충분히 잘 정의되어 있지만 오버플로가 제한되지 않은 경우 컴파일러가 일반적으로 유용한 추론을하는 것을 막지는 않습니다. 정의되지 않은 행동. 컴파일러가 많은 경우 “최적화”를하는 것을 막을 수 있는데, 그 주요한 효과는 프로그래머가 이러한 “최적화”를 막는 유일한 목적을 가진 코드에 추가적인 혼란을 추가하도록 요구하는 것입니다. 그것이 좋은 것인지 아닌지는 자신의 관점에 달려 있습니다.