2013년 2월 4일 월요일

C4018 워닝을 잡아라!

C4018 warning을 아시나요?
Visual Studio 에서 뿌려주는 컴파일 워닝 코드입니다. (cl.exe 가 뿌려주는거라고 말하는게 정확한 것이겠죠?)
어찌되었거나 Level3 짜리 워닝인데요, "signed int 와 unsigned int 간의 비교연산이 있다" 라는 경고입니다.

여러분은 어떤가요? 컴파일러가 이런 워닝을 뱉어내면 어떻게 하시나요?
"워닝은 반드시 잡아야 하는 대상이 아닌 그냥 무시해도 되는거..." 정도로 생각하시는 분들이 생각보다 많더군요.
C4018 때문에 발생하는 재앙(?)을 한번 볼까요?


void test_compare_uint_int()
{
    int32_t  i32;
    uint32_t u32;

    i32 = 1;
    u32 = 10;  
    printf("i32 %s u32 \n", i32 > u32 ? ">" : "<");

    i32 = -1;
    u32 = 10;
    printf("i32 %s u32 \n", i32 > u32 ? ">" : "<");
}

위의 코드는 i32(signed) 와 u32(unsigned) 간의 비교연산을 시도하는 코드입니다.
실행해보면 어떤 결과가 나올까요?
첫 번째는 비교는 사실 문제가 없습니다.
두 번째는 어쩌면 생각과는 다른 결과가 나올 수 도 있을 겁니다.
상식적으로 -1과 10 중에 누가 더 클까요? 사람은 10이 더 크다고 생각하겠지만 컴퓨터도 그럴까요?
-1 == 0xFFFFFFFF, 10 == 0x0000000a 입니다. 누가 더 큰가요? 당연히 0xFFFFFFFF 입니다.
이게 결국 의도하지 않은 버그를 만들어내게 되지요.
이런 류의 버그는 평소엔 잠잠히 있다가 아주 가끔씩, 비 정기적으로 (보통때는 양의 정수이다가 아주 가끔 음수값이 들어올때) 시스템을 망가뜨리게 되죠.
당연히 디버깅하기가 매우 까다로운 녀석중에 하나입니다.

그럼 어떻게 해야 할까요?
정석대로라면 signed 와 unsigned 를 용도에 따라서 정확히 구분해서 사용하는거죠.
하지만 그게 말처럼 간단하지 않습니다. 애매한 경우가 많이 생기죠.
외부 라이브러리가 리턴하는 값이 signed 인데 내 코드의 비교 대상 변수는 unsigned 라든지...

그럼 어쩌라구?!
그렇습니다. signed 변수와 unsigned 변수를 모두 담을 수 있는 크기의 변수로 캐스팅하는겁니다.

i32 = -1;
u32 = 10;
/* 이렇게 하면 안되요!! */
printf("i32 %s u32 \n", i32, i32 > u32 ? ">" : "<", u32);

/* 이렇게 해야 합니다. */
printf("i32 %s u32 \n", i32, INT64(i32) > INT64(u32) ? ">" : "<", u32);

오늘의 교훈! 컴파일러의 워닝 메세지도 모두 없애는 습관을 들이자.

댓글 2개:

  1. sign extension 때문에 결국 똑같은 문제가 발생하지 않나요?

    답글삭제
    답글
    1. 두개의 변수를 모두 담을 수 있는 충분히 큰 사이즈로 키우면 MSB 로 인식되던 비트가 MSB 비트가 아니기때문에 문제가 발생하지 않습니다.

      삭제