VC++에서 bool이 가지는 의미.

@codemaru · April 25, 2007 · 13 min read

C에는 없는데 C++에 있는것 중의 하나가 bool입니다. true, false를 표현하는데 사용하는 데이터 타입이죠. 그런데 요놈이 참 좋은거 같은데 귀찮습니다. 아래 코드를 vs .net 2003 이상의 컴파일러에서 컴파일을 하면 성능 경고가 발생합니다. C4800이죠. bool보다 큰 타입에서 변환을 하는 경우는 다 발생합니다. 왜 그럴까요?

int \_tmain(int argc, \_TCHAR\* argv[])  
{  
    int a = 4;  
    bool b = a;  
    return 0;  
}

궁금해서 어셈블리로 변환을 해보았습니다. 성능 경고가 뜰만하군요. ㅋ 한 바이트를 변환하기 위해서 0과 비교하고 그 결과를 취하고 있습니다.

; bool b = a;
cmp    DWORD PTR _a$[ebp], 0
setne al
mov    BYTE PTR _b$[ebp], al
귀찮은 성능경고를 피하기 위해서는 아래와 같은 편법을 써야 합니다. 변환이 아닌 비교를 한다고 컴파일러를 속이는 것이죠. 아래 코드를 디어셈 해보면 아시겠지만 위 코드랑 별반 차이가 없습니다.

int \_tmain(int argc, \_TCHAR\* argv[])  
{  
    int a = 4;  
    bool b = (a != 0); // a ? true : false;  
    return 0;  
}

이쯤되면 보통 많은 분들은 이렇게 말씀하십니다. bool이 비교를 할 때에 더 좋으라고 만든거다. 저런 황당한 식으로 변환하는 코드가 더 잘못된 것이다. 그럼 과연 bool은 int보다 비교에서 더 성능이 뛰어날까요. 당연히 아닙니다. 1바이트인 bool을 비교하기 위해서 컴파일러는 내부적으로 movzx를 사용해서 bool을 4바이트로 확장합니다. 그리고 0과 비교하죠. 1바이트 bool은 한 마디로 공간 외에는 어떠한 경우에도 이득이 없습니다. 왜냐하면 CPU가 지금은 8비트가 아니기 때문입니다. 32비트 CPU에서는 32비트 자료형이, 64비트 CPU에서는 64비트 자료형이 가장 효율적입니다. 왜냐하면 CPU가 그것들을 기본 단위로 취급하기 때문이죠.

그렇다면 똑똑한 사람들이 만드는 컴파일러는 왜 대부분 bool을 1바이트로 구현했을까요? 메모리를 아끼기 위해서 그랬을까요? 만약 그랬다면 적어도 저는 bool 사이즈를 4바이트로 만드는 옵션을 제공해야 했다고 생각합니다. 안타깝게도 VC++에는 그런 옵션이 없었습니다. 그렇다면 표준에서 1바이트로 만들어!!! 라고 말했을까요? 그런것도 아닙니다. 표준은 사이즈에 대해서 아래와 같이 언급하고 있습니다. char 사이즈 외에는 전부 implementation-defined입니다. 구현하는 사람 멋대로 만들면 되는 거죠.

sizeof(char), sizeof(signed  char)  and sizeof(unsigned char) are 1; the result of sizeof applied to any other fundamental  type (_basic.fundamental_) is implementation-defined. [Note: in particular,   sizeof(bool) and sizeof(wchar_t) are implementation-defined.15) ]

bool은 1바이트, int는 4바이트 이렇게 생각하면 일견 int가 엄청난 공간을 낭비하는 것 처럼 보입니다. 변수가 네 개가 되면 4 : 16이 되어 버리죠. 하지만 실제로는 이렇게 단순한 문제가 아닙니다. 여기서는 단순히 저장 공간을 비교한 것이기 때문이죠. 비교에 들어가는 코드 크기 까지 고려한다면 이렇게 단순하지 않습니다.

32비트 컴퓨터에서 int를 0과 비교하는데에는 cmp 명령어 하나로 4바이트가 소모됩니다. 하지만 bool을 false와 비교하는데는 movzx, test를 사용해야 하기 때문에 6바이트가 소모되죠. 즉 bool이 2바이트를 더 사용하는 것 입니다. 한 변수에 대해서 비교가 두 번 발생한다면 본전이고, 세 번 발생한다면 int가 2바이트를 절약하는 셈이 됩니다.

실제로 bool과 int의 사이즈 문제는 코드 공간과 저장 공간 사이의 절충 문제입니다. 엄청나게 많은 환경 변수를 bool로 저장하고 실제로 비교는 거의 하지 않는 경우와, bool 변수는 몇 개 안되지만 엄청 많이 비교하는 경우가 다르다는 것이죠. 전자가 bool이 유리하다면 후자는 int가 유리합니다. 그래서 똑똑한 개발자가 이런 것을 모두 고려해서 전자와 같은 결정을 했다고 가정해 봅시다. 그렇다면 각종 값을 변환해서 저장하는 과정에서 경고가 뜹니다. 후자와 같이 결정을 한 경우라면 bool 변수를 쓰고 싶지만 bool의 장점을 활용할 수 없다는 단점이 있죠. 이러한 문제점을 해결하기 위해서는 bool의 사이즈를 변경할 수 있는 컴파일러 옵션이 있어야 합니다.

bool이 단지 사이즈 이슈만 가지고 있는것도 아닙니다. 그 유명한 vector 이슈도 있죠. vector은 형태는 bool을 저장하는 컨테이너처럼 보이지만 실제로는 템플릿 특화로 비트셋을 사용해서 처리하도록 되어 있습니다. 모르고 사용했다간 낭패를 겪을 수 있죠. 이걸 왜 비트셋으로 특화했는지는 더더욱 이해가 되질 않습니다. ㅠㅠ

4바이트 옵션??
bool에 관한 컴파일러 옵션이 생기면 어떨까요? 좋을까요? 나쁠까요? 어떤 개발자는 4바이트로 쓰고, 어떤 개발자는 1바이트로 쓰고, 지옥같을 까요? 물로 모든 문제에는 일장일단이 있기 마련이지만 저는 그렇게 많이 혼란스럽다고 생각하진 않습니다. 좀 더 융통성이 있다고 생각합니다.

과거에 제가 사용했던 C++ Builder에 포함된 컴파일러는 enum 사이즈에 관한 옵션을 가지고 있었습니다. 빌더의 경우 enum을 기본적으로 1바이트를 사용하고 옵션을 켜면 4바이트로 조정할 수 있습니다. 그 당시 저희 회사는 Visual C++과 빌더를 혼용해서 사용했죠? 제가 행복했을까요? 최악이었습니다. 그 옵션을 몰랐을 당시에 빌더에서 Visual C++ DLL 함수만 호출하면 엉뚱해졌기 때문입니다. 나중에야 enum 크기가 다른것을 알게되었고 옵션을 통해서 조정할 수 있다는 것을 알았죠. 그것을 알기까지 애꿎은 디버깅만 엄청 했었죠.

앞선 사례에서 만약에 빌더에서 옵션을 제공하지 않았다면 저는 enum으로 된 코드를 모두 고쳐야 했거나 적어도 빌더용 헤더는 별도로 제작했어야 합니다. 하지만 옵션을 제공함으로써 헤더 변경 없이 문제를 해결할 수 있었죠. 반면에 Visual C++ 컴파일러는 사이즈에 관한 어떤 옵션도 제공하지 않습니다.

정성태님의 "VC++에서 bool이 가지는 의미."를 읽으시면 사이즈에 관한 좀 더 상세한 내용을 보실 수 있습니다. 정성태님의 글에도 나오지만 사실 제가 위에까지 쓴 내용은 좀 트리키합니다. 사실 이 주제는 아래 댓글을 달아주신 김형욱님께서 제시한 것 입니다. 강제 캐스팅을 해도 컴파일러가 경고한다는 것이었죠. 그래서 왜 그럴까? 하고 알아보던 차에 적게된 내용입니다. 저도 사실 이런 식의 분석을 그다지 좋아하진 않습니다. ㅎㅎ 대세에 큰 영향을 주는 것도 아니죠. ㅋ

결론,.. VC++에게 bool은 어떤 의미일까요???

제가 bool에 관해서 굉장히 냉소적으로 썼지만 사실 int로는 bool을 따라갈 수 없는 부분이 있긴 합니다. 바로 형식 안전성과 관련된 부분입니다. bool 자체가 원래 true, false만 저장할 수 있도록 만들어진 자료형이란 이야기죠. 아래 댓글에서 김형욱님께서 제시한 페이지에 자세한 설명이 있습니다. 간단하게 세 단계를 통해서 스스로 bool의 타입 안전성을 깨닫도록 하고 있습니다.

void f(int b);  
void f(BOOL b);

작위적으로 보이지만 위의 코드는 컴파일되지 않습니다. BOOL과 int가 동일하기 때문이죠. 이 문제는 BOOL을 enum으로 정의하면 해결할 수 있습니다.

BOOL b = 37;  
if(b == TRUE)

C언어로 개발을 해 보신 분이라면 누구라도 겪었을 법한 문제입니다. 저 또한 저런 문제를 겪은 적이 있었죠. 그래서 저런 코드는 되도록 잘 사용하지 않습니다. if(b) 내지는 if(!b)로 사용하죠. 성공 코드가 ERROR_SUCCESS 같은 경우만 비교를 해서 사용합니다. 이 단계를 우아하게 통과하려면 BOOL을 클래스로 만들면 됩니다.

f(3<4);

초특급 핵폭탄입니다. 저는 미처 몰랐던 부분이기도 하구요. 비교 변환의 결과가 bool이 된다는 것이죠. 첫 번째 제시된 문제같이 하면 이건 양쪽다 변환될 수 있게 됩니다. 클래스로 만들면 안되나요? 안됩니다. C++에서는 builtin 타입에 대한 오버로딩은 못합니다.

void f(BOOL b) { printf("BOOL\n"); }  
void f(int b) { printf("int\n"); }  
f(3<4);

결론은 위 코드에서 BOOL이 출력되도록 할 수 없다는 겁니다. 그럴싸하게 이것을 해결할 수 있는 방법이 있을까요?

@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중