unsigned int의 오버플로우

C’s unsigned integer types are ‘‘modulo’’ in the LIA−1 sense in that overflows or out-of-bounds results silently wrap.

unsigned int는 오버플로우가 없습니다.

표현 가능한 개수로 나눈 나머지가 된다고 정의되어 있습니다.

#include <stdio.h>

int main(void)
{
    unsigned int num1;
    unsigned int num2;
    const char  *result;

    scanf("%d %d", &num1, &num2);
    result = "!(%u > %u && %u > %u)";
    if (num1 > num2 && num1 + 1 > num2)
        result = "%u > %u && %u > %u";
    printf(result, num1, num2, num1 + 1, num2);
    return (0);
}

입력으로 4294967295 0를 넣으면 !(4294967295 > 0 && 0 > 0)가 되는 것을 볼 수 있습니다.

-O3 플래그로 컴파일러 최적화를 활성화해도 마찬가지입니다. unsigned int는 원래 이렇게 동작합니다.

signed int의 오버플로우

C/C++에서 signed int의 오버플로우는 정의되지 않았습니다.

왜 아직도 정의되지 않았는지 의아할 수 있는데 컴파일러 최적화 때문입니다.

수학적으로 볼 때 A가 B보다 크다면, A + 1도 당연히 B보다 클 것입니다.

이 점을 이용해 컴파일러가 최적화를 할 수도~~, 안 할 수도~~ 있습니다.

#include <stdio.h>

int main(void)
{
    int         num1;
    int         num2;
    const char  *result;

    scanf("%d %d", &num1, &num2);
    result = "!(%d > %d && %d > %d)";
    if (num1 > num2 && num1 + 1 > num2)
        result = "%d > %d && %d > %d";
    printf(result, num1, num2, num1 + 1, num2);
    return (0);
}

문제가 없을 수도 있습니다. 일단 여기서는 -O3 플래그로 최적화를 활성화 해 보겠습니다.

2147483647 0를 입력하면 2147483647 > 0 && -2147483648 > 0가 출력됩니다.

(num1 + 1 > num2), 즉 -2147483648 > 0true라는 뜻이죠.

하지만 컴파일러는 이 식을 (num1 > num2)으로 최적화할 수 있습니다.

undefined behavior의 위험성?

undefined behavior는 말 그대로 정의되지 않은 동작이지 반드시 다르게 동작하는 것이 아닙니다.

어떻게 동작할 것이라고도 기대해서는 안 됩니다.

어떤 결과가 나올지 모르기 때문에, undefined behavior에 의존하면 안 됩니다.

printf(”%s”, NULL)은 대부분 (null)을 출력하지만 C 표준에서는 이에 대해 정의하지 않습니다.

glibc의 경우에는 문서에 printf(”%s”, NULL)(null)을 출력한다는 내용이 있습니다.

glibc처럼 정의된 경우가 아니라면 printf(”%s”, NULL)는 undefined behavior입니다.

printf(”%s”, NULL)가 오류를 내지 않을 것이라고 기대해서는 안 됩니다.

실제로는 오류가 날 수 있고, 오류가 나도 그 printf는 잘못한 것이 없습니다.

<aside> ⚠️ Makefile에서 re: fclean all로 쓰는 것 역시 undefined behavior에 의존하는 것입니다.

(참고: re: fclean all을 쓰지 마세요!)

</aside>