9. Boolean Expressions and Variables

C provides no formal, built-in Boolean type. Boolean values are just integers (though with greatly reduced range!), so they can be held in any integral type. C interprets a zero value as “false” and any nonzero value as “true.” The relational and logical operators, such as ==, !=, <, >=, &&, and ||, return the value 1 for “true,” so 1 is slightly more distinguished as a truth value than the other nonzero values (but see question [*]9.2).

C 언어는 C99 표준 다음부터 이제 boolean 타입을 지원하지만, 문제는 boolean 타입의 필요성에 따라, 표준으로 제정되기 이전부터 개발자들이 임의로 boolean 타입을 만들어 써 왔다는 것입니다. 따라서, 기존 프로그램들과 호환성을 위해서, boolean 타입의 이름은 (이상하지만) _Bool입니다. 좀 더 자세한 것은 질문 [*]9.1을 보기 바랍니다.



Q 9.1
C 언어에서 불리언(boolean) 값으로 쓸 수 있는 괜찮은 타입이 있을까요? 그리고 왜 그런 타입이 표준으로 지정되어 있지 않는 거죠? 참(true)과 거짓(false)을 나타내기 위해 #define으로 정의를 하는 게 좋을까요, 아니면 enum을 쓰는 것이 좋을까요?

Answer
C 언어는 표준으로 제공하는 불리언(boolean) 타입이 없습니다. 왜냐하면, 불리언 타입은 항상 공간/처리시간의 `tradeoff' 문제를 가지기 때문입니다. 따라서 이는 프로그래머가 알아서 결정하도록 해 놓은 것입니다. (int를 쓰면 처리가 빠를 수 있고, char를 쓰면 공간을 절약할 가능성이 있습니다.9.1 그리고, 공간을 절약하기 위해 작은 데이터 타입을 썼다면, 나중에 int 타입으로 변환하는 코드가 많이 필요할 경우, 오히려 코드의 크기가 커지고 느려질 수 있다는 단점이 있습니다.)

#defineenum 중 어느 것을 쓸 것인지는 그리 큰 문제가 되지 않습니다 (질문 [*]2.22와 [*]17.10을 참고하기 바랍니다.) 다음 둘 중 아무 것이나 써도 좋으며, 0과 1을 직접 써도 좋습니다:

  #define TRUE  1             #define YES 1
  #define FALSE 0             #define NO  0

  enum bool {false, true};    enum bool {no, yes};
그러나 일단 어떤 것을 쓰겠다고 정했다면, 한 프로그램이나 프로젝트 내에서는 계속 그것을 쓰는 것이 좋습니다. (그러나 디버거에서 변수의 값을 검사할때, 수치가 아닌 이름으로 보여 줄 수 있으므로 enum이 더 좋을 수도 있습니다. (Note 질문 [*]2.22 참고)

다음과 같이 typedef를 쓸 수도 있습니다:

  typedef int bool;
또는
  typedef char bool;
또는
  typedef enum { false, true } bool;

어떤 사람들은 다음과 같이 쓰는 것을 선호합니다:

  #define TRUE (1==1)
  #define FALSE (!TRUE)
또는 다음과 같이 “도우미(helper)” 매크로를 만듭니다:
  #define Istrue(e) ((e) != 0)

그렇지만 이런 방식이 특별히 좋다는 것은 아닙니다. (질문 [*]9.2를 참고하기 바랍니다; 질문 [*]5.12와 [*]10.2도 보시기 바랍니다).

Note
C99에서 _Bool 타입을 제공하기 때문에, 위 설명은 이제 올바른 답변이 아닙니다. C 언어는 boolean 타입을 제공하며, 타입 이름은 _Bool입니다. 깔끔한 이름이 아닌, 이렇게 이상한 이름을 쓰는 이유는, 기존의 많은 C 프로그램들이, 위에서 설명한 것처럼 나름대로 boolean 타입을 이미 쓰고 있기 때문에, 이들 프로그램들과 호환을 위해서 이러한 이름을 쓰게 되었습니다. 표준 헤더 파일 <stdbool.h>를 포함시키면, 매크로 bool9.2_Bool로 정의되어 있기 때문에, 다음과 같이 쓸 수 있습니다:
  #include <stdbool.h>

  _Bool a;    /* okay */
  bool  b;    /* okay, using macro `bool' */
또, <stdbool.h>는 매크로 true, false를 정의하고 있기 때문에 각각 1, 0 대신으로 쓸 수 있습니다.

Boolean 타입이 올 자리에 다른 정수 타입을 쓰게되면, 자동으로 변환되며, 아시다시피, 0이 아닌 모든 값은 1로, 0은 0으로 바뀝니다.

또한, C 언어가 boolean 타입을 지원한다고 해서, C++ 처럼, 비교 연산자들의 비교 결과가 _Bool 타입인 것은 아닙니다. 비교 연산자의 비교 결과는 여전히 int인 것을 주의하기 바랍니다. 여기에서 비교 연산자라고 하는 것은, 표준에서 relational operator와 equality operator를 포함한 것입니다. 즉, <, <=, >, >=, ==, !=가 여기에 해당합니다.

References
[C89] § 6.2.5 p. 33, § 6.3.1.2 p. 43, § 6.5.8 p. 86, § 6.5.9 p. 86 § 7.16 p. 252
[H&S2002] § 5.1.2 p. 128, § 5.1.5 p. 132 § 7.6.4 p. 233



Q 9.2
#define을 써서 TRUE를 1로 만드는 것은 위험하지 않을까요? C 언어에서는 0이 아닌 비트가 하나라도 있으면 참(true)으로 인식될 테니까요. 만약에 어떤 함수나 연산자가 1이 아닌 다른 값을 리턴할 경우 어떻게 되는 거죠?

Answer
맞습니다. 사실 C 언어에서는 0이 아닌 모든 값이 참(true)입니다. 그러나 이것은 단지 “입력”일 경우에만 의미를 가집니다; 다시 말하면 어떤 불리언 값이 예상되는 경우에만 그 의미를 가집니다. 만약 불리언 값이 내장된(built-in) 연산자에 의해 만들어진 것이라면 분명히 0 또는 1의 값을 가지게 됩니다. 따라서 다음과 같이 코드를 만들어도 안전합니다 (TRUE가 1이라고 가정할 때):
  if ((a == b) == TRUE)
그러나 위와 같이 직접 TRUEFALSE와 같은 지 비교하는 것은 무의미할 뿐더러, 매우 위험합니다. 왜냐하면 어떤 라이브러리 함수들은 (특히 isupper(), isalpha(), 등등) 참(true)을 나타내기 위해 `0이 아닌 값'을 씁니다. 즉, 이 때 `0이 아닌 값'은 꼭 1일 필요가 없습니다.

(또, 만약 여러분이 아래 코드보다

  if (a == b)
다음 코드가 더 향상된 것이라고 믿는다면,
  if ((a == b) == TRUE)
왜 다음과 같이 쓰지 않나요?
  if (((a == b) == TRUE) == TRUE)
아예 다음과 같이 쓰는게 더 좋은 것이라고 믿는 것인가요?
  if ((((a == b) == TRUE) == TRUE) == TRUE)
Lewis Caroll의 에세이, “What the Tortoise Said to Achilles.”를 읽어보시기 바랍니다.)

if (a == b)가 완벽하게 올바른 것과 마찬가지로, 아래 코드도 완벽히 올바른 코드입니다:

  #include <ctype.h>
  ...
  if (isupper(c)) {
    ...
  }
왜냐하면, isupper는, 참/거짓을, zero/nonzero로 알려주기 때문입니다. 비슷하게, 아래 코드는 좋은 코드입니다:
  int isvegetable;   /* really a bool */
  ...
  if (isvegetable) {
    ...
  }
마찬가지로, 아래 코드도 좋습니다:
  extern int fileexists(char *);  /* returns true/false */
  ...
  if (fileexists(outfile)) {
    ...
  }
위 두 예에서 isvegetablefileexists()는 개념상 boolean 타입입니다. 따라서 다음과 같이 쓰는 것은 전혀 개선이라고 할 수 없습니다:
  if (isvegetable == TRUE)
또는,
  if (fileexists(outfile) == YES)
(이러한 스타일이 좀 더 “안전한” 또는 “좋은 스타일”이라고 생각한다면, 정말로 위험하고 나쁜 스타일을 믿고 있다고 말할 수 있습니다. 가독성도 오히려 떨어집니다. 질문 [*]17.10을 참고하기 바랍니다.)

여기에서 배울 가장 중요한 규칙은, 값을 대입할 때, 함수의 인자로 전달할 때, 또는 불리언 값을 리턴할 때에만 TRUEFALSE를 쓴다입니다. 즉, 비교할 때에 이런 것을 쓰는 것은 좋지 않습니다. (질문 [*]5.3 참고)

TRUEFALSE와 같은 (또는 YESNO) 매크로를 쓰는 것은 어떻게 보면 좋은 것 같지만 C 언어에서 불리언 값이라는 개념이 헷갈리기 때문에, 어떤 프로그래머들은 0과 1이라는 수치를 직접 쓰는 것을 선호하기도 합니다. (질문 [*]5.9를 참고하기 바랍니다.)

References
[K&R1] § 2.6 p. 39, § 2.7 p. 41
[K&R2] § 2.6 p. 42, § 2.7 p. 44, § A7.4.7 p. 204, § A7.9 p. 206
[C89] § 6.3.3.3, § 6.3.8, § 6.3.9, § 6.3.13, § 6.3.14, § 6.3.15, § 6.6.4.1, § 6.6.5
[H&S] § 7.5.4 pp. 196-7, § 7.6.4 pp. 207-8, § 7.6.5 pp. 208-9,
[H&S] § 7.7 pp. 217-8, § 7.8 pp. 218-9, § 8.5 pp. 238-9, § 8.6 pp. 241-4 Carroll, “What the Tortoise Said to Achilles”
Note
C99는 표준 헤더 파일, <stdbool.h>을 통해서 매크로 truefalse를 제공하며, 각각 0과 1로 정의되어 있습니다. 질문 [*]9.1에서 새 boolean type인 _Bool을 참고하기 바랍니다.



Q 9.3
p가 포인터일때, if (p)와 같은 코드를 쓰는 것은 나쁜가요?
Answer
아닙니다. 좋습니다. 질문 [*]5.3을 참고하기 바랍니다.



Q 9.4
TRUEFALSE와 같은 심볼을 쓰는 것이 좋을까요, 아니면, 1과 0을 직접 쓰는 것이 좋을까요?
Answer
그것은 여러분이 결정할 문제입니다. Preprocessor 매크로인 TRUE, FALSE는 (물론 NULL 포함) 단지 코드를 읽기 쉽게 하기 위해 쓰는 것이지, 이들 매크로가 나타내는 값이 바뀔 가능성이 있기 때문은 아닙니다. (질문 [*]5.10, [*]17.10 참고) 즉 이것은 스타일에 관한 문제이지 옳고 그른 문제가 아닙니다.

어떤 사람들은 TRUEFALSE같은 심볼 이름을 쓰는 것은, 코드를 읽는 사람에게 boolean 값이 쓰인다는 것을 알려주는 역할을 하기 때문에, 좋다고 합니다. 또 어떤 사람들은 어차피 C 언어에서 boolean 값과 정의가 혼동스럽기 때문에, TRUEFALSE같은 매크로를 쓰는 것은 오히려 더 복잡하게 만든다고 생각합니다. (질문 [*]5.9 참고)



Q 9.5
다른 곳에서 라이브러리를 받아 왔는데, 그 라이브러리에서 이미 TRUEFALSE를 다르게 정의하고 쓰고 있어서, 제 코드와 충돌이 일어납니다.

Answer
질문 [*]10.10을 보기 바랍니다.

Seong-Kook Shin
2018-05-28