IT이야기

GCC를 사용하는 x86의 정수 오버플로가 무한 루프를 일으키는 이유는 무엇입니까?

cyworld 2022. 7. 2. 13:13
반응형

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

정의되지 않은 행동이라고 표준이 말하는 것은 진심입니다.뭐든지 발생할 수 있다."무엇이든"에는 "보통 정수는 감겨있지만 가끔 이상한 일이 일어난다"가 포함됩니다.

네, x86 CPU에서는 일반적으로 정수는 예상대로 랩됩니다.이것은 그러한 예외 중 하나입니다.컴파일러는 정의되지 않은 동작을 일으키지 않는다고 가정하고 루프 테스트를 최적화합니다.랩어라운드를 원하시면 패스해주세요.-fwrapv로로 합니다.g++ ★★★★★★★★★★★★★★★★★」gcc컴파일 할 때 명확하게 정의된 (2개의 평가) 오버플로우 시멘틱을 제공하지만 퍼포먼스가 저하될 수 있습니다.

되지 않은 - 특히 예: - 최적화)에 의한 동작(예: 정의되지 않음-O2)가 켜짐 - 모든 일이 일어날 수 있음을 의미합니다.

-O2환합니니다다

참고로 icl과 tcc는 잘 작동하지만, 그런 것에 의존해서는 안 됩니다.

이에 따르면 gcc 최적화는 실제로 서명된 정수 오버플로를 이용합니다.이는 "버그"가 의도된 것임을 의미합니다.

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

컴파일러는 정의되지 않은 동작의 존재를 자유롭게 사용하여 최적화를 개선할 수 있습니다(이 예와 같이 루프에서 조건부 삭제).C++ 레벨의 구조와 x86 레벨의 머신 코드 구조 간의 매핑은 머신 코드가 실행되었을 때 C++ 추상 머신에 의해 요구되는 결과를 생성한다는 요건 이외에는 보장되지 않으며 심지어 유용하기도 하다.

i += i;

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

-fwrapv를 사용하면 정확합니다. -fwrapv

제발, 정의되지 않은 행동은 바로 그거야, 정의되지 않은 거야.무슨 일이든 일어날 수 있다는 뜻이에요.실제로 (이 사례와 같이) 컴파일러는 호출되지 않을 것이라고 가정할 수 있으며, 그것이 코드를 더 빠르고 작게 만들 수 있다면 하고 싶은 대로 할 수 있습니다.실행되어서는 안 되는 코드에 무슨 일이 일어날지는 아무도 모릅니다.주변 코드(컴파일러가 다른 코드를 생성할 수 있음), 사용된 변수/정수, 컴파일러 플래그 등에 따라 달라집니다.아, 그리고 컴파일러가 업데이트 되어 같은 코드를 다르게 쓸 수도 있고 코드 생성에 대한 다른 뷰를 가진 다른 컴파일러를 얻을 수도 있습니다.또는 다른 머신을 구입해도, 같은 아키텍처 라인의 다른 모델에서도, 독자적인 미정의의 동작(미정의의 opcode를 검색해 보면, 일부의 기업적인 프로그래머가, 그러한 초기 머신에서 유용한 것을 알 수 있습니다."컴파일러가 정의되지 않은 동작에 대해 명확한 동작을 제공한다"는 은 없습니다.구현에 정의되어 있는 영역이 있기 때문에 컴파일러가 일관되게 동작하는 것을 기대할 수 있습니다.

컴파일러가 정수 오버플로를 정의되지 않은 동작의 "비중요한" 형태로 간주해야 한다고 지정한 경우에도(부록 L에서 정의된 바와 같이), 정수 오버플로의 결과는 보다 구체적인 동작에 대한 특정 플랫폼 약속이 없으면 최소한 "부분적으로 불확실한 값"으로 간주해야 합니다.이러한 규칙 하에서 1073741824+1073741824를 더하면 임의로 2147483648 또는 -2147483648 또는 2147483648 mod 4294967296과 일치하는 다른 값이 산출되는 것으로 간주될 수 있으며, 추가에 의해 얻어진 값은 임의로 mod 4294967296과 일치하는 값으로 간주될 수 있습니다.

오버플로우가 "부분적으로 불확실한 값"을 생성하는 규칙은 Annex L의 문자와 정신을 따르도록 충분히 정의되어 있지만, 오버플로우가 제약되지 않은 미정의 동작일 때 정당화될 수 있는 것과 같은 일반적으로 유용한 추론을 컴파일러가 하는 것을 막지는 않습니다.컴파일러가 가짜 최적화(optimization)를 하는 것을 막을 수 있는데, 그 주된 효과는 프로그래머가 그러한 최적화(optimization)를 막는 것이 유일한 목적인 코드에 더 많은 난잡함을 추가하도록 요구하는 것이다.

언급URL : https://stackoverflow.com/questions/7682477/why-does-integer-overflow-on-x86-with-gcc-cause-an-infinite-loop

반응형