program story

이것이 정의되지 않은 동작 인 이유는 무엇입니까?

inputbox 2020. 12. 8. 08:03
반응형

이것이 정의되지 않은 동작 인 이유는 무엇입니까?


이 질문에 대한 나의 대답 은 다음과 같습니다.

inline bool divisible15(unsigned int x) 
{
    //286331153 = (2^32 - 1) / 15
    //4008636143 = (2^32) - 286331153
    return x * 4008636143 <= 286331153;
}

VS2008 컴파일러를 사용하여 내 컴퓨터에서 완벽하게 작동했지만 여기서는 전혀 작동하지 않습니다.

누구든지 아이디어가 있습니까? 왜 다른 컴파일러에서 다른 결과를 얻습니까? unsigned오버플로는 정의되지 않은 동작이 아닙니다.

중요 사항 : 일부 테스트 후 나머지 부분을 15로 나누는 것보다 더 빠릅니다. (그러나 모든 컴파일러에 적용되지는 않음)


정의되지 않은 동작이 아니라 C89와 C99 사이의 C 언어 표준의 주요 변경 사항입니다.

C89에서 4008636143 같은 상수 정수 그건에 맞지 않는 int또는 long int그러나이에 적합 할 unsigned int부호이지만, C99, 그들은 하나 있습니다 long int또는 long long int(하나의 값을 저장할 수있는 작은 일 중에 따라 다름). 결과적으로 모든 표현식은 64 비트를 사용하여 평가되어 오답이됩니다.

Visual Studio는 C89 컴파일러이므로 C89 동작이 발생하지만 Ideone 링크는 C99 모드에서 컴파일됩니다.

다음을 사용하여 GCC로 컴파일하면 더 분명해집니다 -Wall.

test.c: In function ‘divisible15’:
test.c:8:3: warning: this decimal constant is unsigned only in ISO C90

C89 §3.1.3.2에서 :

정수 상수의 유형은 해당 값이 표시 될 수있는 해당 목록의 첫 번째입니다. 비접 미 십진수 : int, long int, unsigned long int; 접미사가없는 8 진수 또는 16 진수 : int, unsigned int, long int, unsigned long int; [...]

C99 §6.4.4.1 / 5-6 :

5) 정수 상수의 유형은 해당 값을 나타낼 수있는 해당 목록 중 첫 번째입니다.

Suffix | Decimal Constant | Octal or Hexadecimal Constant
-------+------------------+------------------------------
none   | int              | int
       | long int         | unsigned int
       | long long int    | long int
       |                  | unsigned long int
       |                  | long long int
       |                  | unsigned long long int
-------+------------------+------------------------------
[...]

6) 정수 상수가 목록에서 어떤 유형으로도 표현 될 수없는 경우 확장 정수 유형이 값을 나타낼 수 있으면 확장 정수 유형을 가질 수 있습니다. 상수 목록에있는 모든 유형이 서명 된 경우 확장 정수 유형이 서명되어야합니다. [...]

완전성을 위해 C ++ 03은 정수 상수가 너무 커서 long int. C ++ 03 §2.13.1 / 2에서 :

정수 리터럴의 유형은 형식, 값 및 접미사에 따라 다릅니다. 십진수이고 접미사가없는 경우 값을 나타낼 수있는 첫 번째 유형이 있습니다. int, long int; 값을로 표현할 수없는 long int경우 동작이 정의되지 않습니다. 이 진수 16 진수이고, 접미사가없는 경우, 그 값을 표시 할 수있는 이러한 유형의 제 갖는다 : int, unsigned int, long int, unsigned long int. [...]

C ++ 11 동작은 C99와 동일합니다 (C ++ 11 §2.14.2 / 3 참조).

하나 C89, C99, C ++ 03 및 C ++ (11)로 컴파일 할 때 코드 동작합니다 일관, 간단한 수정과를 부기 부호없는 정수 4,008,636,143 수 있도록하는 것입니다 있는지 확인하기 u로를 4008636143u.


int상수 를 사용하기 때문에 컴파일러는 코드를 바로 가기 위해 정의되지 않은 오버플로를 "사용"할 수 있습니다. 아래와 같이 U로 수정하면 "작동"합니다.

inline bool divisible15(unsigned int x) 
{
    //286331153 = (2^32 - 1) / 15
    //4008636143 = (2^32) - 286331153
    return x * 4008636143u <= 286331153u;
}

테스트 :

#include <iostream>


inline bool divisible15a(unsigned int x) 
{
    //286331153 = (2^32 - 1) / 15
    //4008636143 = (2^32) - 286331153
//    return x * 4008636143 <= 286331153;
    return x * 4008636143u <= 286331153;
}

inline bool divisible15b(unsigned int x) 
{
    //286331153 = (2^32 - 1) / 15
    //4008636143 = (2^32) - 286331153
//    return x * 4008636143 <= 286331153;
    return x * 4008636143 <= 286331153;
}


int main()
{
    for(unsigned int i = 0; i < 100; i++)
    {
    if (divisible15a(i))
    {
        std::cout << "a:" << i << std::endl;
    }
    if (divisible15b(i))
    {
        std::cout << "b:" << i << std::endl;
    }
    }
}

산출:

a:0
b:0
a:15
a:30
a:45
a:60
a:75
a:90

암호:

_Z12divisible15aj:
.LFB1192:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    -4(%rbp), %eax
    imull   $-286331153, %eax, %eax
    cmpl    $286331153, %eax
    setbe   %al
    popq    %rbp
    ret

_Z12divisible15bj:
.LFB1193:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    %edi, -4(%rbp)
    movl    -4(%rbp), %edx
    movl    $4008636143, %eax
    imulq   %rdx, %rax
    cmpq    $286331153, %rax
    setle   %al
    popq    %rbp
    ret

그렇습니다. 저는 이것이 32 비트 정수에 맞지 않는다는 Carl / Adam의 분석에 동의하므로 long또는 long long.

참고 URL : https://stackoverflow.com/questions/18706859/why-is-this-an-undefined-behavior

반응형