BOOL 과 bool 의 차이점
bool은 C++의 기본 데이터 형이고
BOOL은 MFC에서 제공되는 데이터형이다.
엄연히 참/거짓을 의미하는 bool 데이터 형이 있음에도 VC++에서는 BOOL로 참/거짓을 표현하고 있다.
왜 그럴까 의아했지만, 이미 MFC의 여러 가지 방식들(매크로를 사용한 메시지 맵 처리 등) 중 일부려거니 넘기고 있었다. 하지만, 이제부터는 내가 개발한 코드에서는 bool로 통일해서 써야겠다.
VC++에서 BOOL을 사용하지 말아야 하는 이유
윈도우즈 프로그래밍 시 보통 참, 거짓을 판별하기 위해 BOOL이라고 하는 데이터 형을 자주 사용한다. 이 BOOL은 windef.h 파일에 아래와 같이 정의되어 있다.
typedef int BOOL;
즉, BOOL은 이름에서 오해할 수 있는 바와 같이 참, 거짓을 표현하는 특별한 어떤 데이터 형이 아니라 단지 int를 재정의한, int와 완전히 동일한 데이터 형일 뿐이다. 그 뿐만 아니라 BOOL 형 변수의 값으로 자주 사용하는 TRUE와 FALSE 역시 windef.h에서 아래와 같이
#define TRUE 1
#define FALSE 0
int 형 상수인 1과 0을 정의한 매크로 상수이다. 따라서
BOOL tmp = TRUE;
이것은
int tmp = 1;
이것과 의미상/문법상으로 똑같다.
그렇다면 왜 굳이 BOOL이라는 재정의 타입을 사용하게 되었을까?
Win32 API는 C를 기반으로 만들어 졌는데 C에는 boolean을 표현하는 별도의 기본 데이터 형이 존재하지 않는다. 따라서 MS에서는 리턴 값이 단지 참, 거짓을 의미하는 함수들에 대해서 가독성을 위해 별도의 구분된 BOOL이라는 데이터 형을 재정의하여 사용하게 된 것이다. 그러나 이러한 BOOL 값을 사용하는 데에는 다음과 같은 주의점과 문제점들이 존재한다.
첫째, BOOL을 참/거짓 외의 상태를 표시하는 데 사용할 경우 모호한 구문을 만들 수 있다.
C 프로그래머들은 전통적으로 어떤 함수가 성공했을 시 0, 실패했을 시 -1(혹은 적절한 에러 코드)를 리턴하도록 프로그래밍한다. 가장 대표적인 예가 int main() 함수로써 보통 정상적으로 종료될 때 return 0;으로 끝내며 비정상 종료 시 exit(-1); 혹은 return -1; 등으로 종료한다.
많은 수의 Win32 API들도 이런 방식으로 되어 있다. 그런데 한편으로 또 다른 Win32의 많은 API들이 그 반대의 형태를 취하고 있다. 즉, 실패하면 0, 성공하면 0이 아닌값을 반환한다.
BOOL은 int 형이므로 이런 함수들의 성공 유무를 판단하기 위해서 리턴 값을 저장하도록 하더라도 문법상으로 아무런 문제가 없다. 하지만 의미상으로 이것은 오해하기 쉬운 여지를 준다. 왜냐하면 여기서 TRUE, FALSE가 의미하는 것은 호출 함수가 무엇인가에 따라 완전히 다른 의미를 가질 수 있기 때문이다.
예를 들어,
BOOL ret = func(); // func()는 정상이면 0, 실패면 -1을 반환하는 함수
if (ret == TRUE)
{
정상 처리
}
return ret;
이런 식으로 처리를 해준다면 잘못된 동작을 수행할 것이다. 이것은 논리 에러이며 따라서 컴파일러는 아무런 에러를(심지어 경고도) 표시하지 않는다. 따라서 이런 종류의 버그는 디버깅하기가 매우 까다롭다.
둘째, TRUE가 반드시 true를 의미하지 않을 수 있다.
즉, TRUE라고 하는 매크로 상수는 컴파일러가 의미하는 참이라는 논리 값의 부분 집합에 불과하다는 사실이다. 예를 들어 보겠다.
BOOL func()
{
BOOL success = AnyFunc(); // AnyFunc()는 실패 시 0,
// 성공 시 0이 아닌 값을 반환하는 함수
if (success == TRUE)
{
TrueFunc();
}
else
{
FalseFunc();
}
return ret;
}
이 함수 구문은 언듯 보기에 AnyFunc()이 성공하면 TrueFunc()를, 실패하면 FalseFunc()를 호출하고 그 반환 값을 리턴하도록 동작할 것이라 생각할 수 있다. 하지만 실상 컴파일러는 "AnyFunc()의 반환 값이 1이면 TrueFunc()를, 1이 아니면 FasleFunc()를 호출하고 그 반환 값을 리턴해라" 라고 받아들이게 된다(물론 위의 예제에서 굳이 success == TRUE라고 명시적으로 비교를 할 필요가 있느냐? 라고 반문할 수도 있겠지만 여기서 중요한 것은 success가 성공 시 TRUE 외의 값을 가질수 있다는 점이다).
셋째, 위의 문제점들이 발생되는 보다 근원적인 문제점이라 할 수 있는 것으로써 BOOL은 타입 안정성이 떨어진다는 점이다.
BOOL은 int 형이기 때문에 그 이름과 달리 참, 거짓이 아니라 int가 수용할 수 있는 모든 정수 값을 저장할 수 있다. 따라서 프로그래머의 의도(순수하게 참과 거짓을 판별해 주는 TRUE/FALSE 값 저장)를 벗어난 값들이 저장될 소지가 있으며 그러한 상황에서도 컴파일러는 아무런 경고를 표시하지 않는다.
결국 BOOL을 사용하게 되면 모호하고 안정적이지 못하며 논리적이지도 못한 - 실용적인 의미에서의 - 가독성이 없는 코드가 되고 만다.
그렇다면 어떻게 해야 할까요?
결론부터 말하자면 BOOL이라는 타입 자체를 사용하지 말아야 한다. BOOL은 MS에서 단지 코드의 문서화 측면에서 재정의한 데이터 형일 뿐이므로 가독성을 배제하면 시스템적으로 혹은 언어적으로 아무런 장점이 없으며 더더구나 가독성 측면에서도 위에서 설명한 바와 같이 오해의 소지가 크므로 전혀 사용해야 할 이유가 없다.
오히려 int를 사용하면 최소한 사람들로 하여금 BOOL에서 기대하게 되는 어떤 잘못된 선입관 같은 것들을 없앨 수 있다.
게다가 C++에서는 bool이라고 하는 참, 거짓 판별 데이터 형이 있으므로 정말로 그런 값들을 원한다면 이 bool을 사용하는 것이 훨씬 좋다(아니 반드시 그렇게 해야 한다).
bool은 int와 완전히 다른 별도의 built-in type이며 bool이 저장할 수 있는 값은 true와 false 단 두 가지 밖에 없다. 그리고 매크로 상수 TRUE, FALSE처럼 'true는 1, false는 0이' 아니라 별도의 독립적인 상태 값이다. 단지 컴파일러에 의해 다른 데이터 형이 bool로 형 변환이 될 때 적절한 변환 규칙에 의해 true나 false로 변환이 될 뿐이다(혹은 그 반대로).
따라서 두 번째 예제를
bool func()
{
bool success = AnyFunc(); // AnyFunc()는 실패 시 0,
// 성공 시 0이 아닌 값을 반환하는 함수
if (success == true)
{
TrueFunc();
}
else
{
FalseFunc();
}
return ret;
}
이렇게 바꿔주면 func()로 넘어가는 인자가 bool이 아니더라도 표준에서 규정하는 명확한 형 변환 규칙에 의해 true 혹은 false로 적절히 바뀌기 때문에(int 값을 bool로 변환 시, 0은 false로 0이 아닌 나머지 값들은 true로 변환된다) 아무런 문제도 없고 형태와 의미가 정확히 일치하는 안정적인 구문이 된다.
보통 윈도우즈에서는 성공 시 0을 반환하는 함수들의 경우, 실패 시에는 실패 원인에 해당하는 에러 코드를 리턴 값으로 반환하므로 이런 경우 bool 변수에 값을 대입하는 것은 옳지 못한 행동이다(이런 경우에는 DWORD나 int 변수를 이용해야 된다).
다행히도 최신의 컴파일러들은 대부분 이런 경우(int나 DWORD 값을 bool 변수에 대입하려는 경우) - 해당 값이 손실될 수 있다는 식의 - 경고를 표시하므로 실수로 그런 행동을 하더라도 프로그래머로 하여금 그러한 사실을 다시 한번 되새기게 해주는 효가 있다(다시 한번 강조하지만 BOOL을 사용하게 되면 그런 경고 메시지는 절대 찾아 볼 수 없다).
최종적으로 정리를 하자면, 이렇게 말할 수 있다.
1. BOOL은 절대 사용하지 말자.
2. BOOL 대신 int, unsigned int, bool을 사용하자.
3. bool 변수를 이용해서 함수 리턴 값 처리 시 통일성을 유지하자.
'C & C++ > C & C++' 카테고리의 다른 글
[TIP] 프로젝트 병합법 (0) | 2011.05.24 |
---|---|
(Tip) 포인터 값으로 추적하는 디버그 팁 (0) | 2011.05.21 |
[Tip] 외부 프로그램 실행 및 종료 (0) | 2011.05.04 |
[Tip] 키 조합으로 프로그램 종료하기 (응용가능) (0) | 2011.05.04 |
API 주요 함수 (0) | 2011.04.29 |
댓글