Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

10. C Preprocessor next up previous contents
Next: 11. ANSI/ISO Standard C Up: C Programming FAQs Previous: 9. Boolean Expressions and Variables

Subsections



10. C Preprocessor

$Id: Site_2fFAQ_2fCfaqHTML_2fnode12_2ehtml,v 1.1 2007/01/09 02:46:17 root Exp root $

C's preprocessor provides reasonable solutions to a number of software enginerring and configuration management problems, although its syntax is unlike the rest of C in several respects. As its name suggests, the preprocessor operates before formal parsing and compilation begin. Because it doesn't know about the structure of the code as seen by the rest of the compiler, it cannot do any processing that relates to declared types or function structure.

The first part of this chapter is arranged around the major preprocessor directives: #define (questions [*]10.1 through [*]10.5), #include (questions [*]10.6 through [*]10.11), and #if (questions [*]10.12 through [*]10.19). Questions [*]10.20 through [*]10.25 cover fancier macro replacement, and finally questions [*]10.26 and [*]10.27 cover a particular set of problems relating to the preprocessor's lack of support for variable-length macro argument lists.



Q 10.1
다음과 같이 간단한, 함수와 비슷한 매크로를 만들려고 합니다.
  #define square(x)   x * x
그런데, 가끔씩 제대로 동작하지 않습니다. 왜 그럴까요?
Answer
매크로 확장(expansion)은 순수하게 텍스트로 이루어집니다. 즉, 쓰여진 그대로 확장된다는 뜻이며, 때때로 (위와 같이) 개발자가 원하지 않은 결과를 가져올 수 있습니다. 함수같은 매크로를 정의할 때에는 다음과 같은 세가지 규칙을 지켜야 합니다:
  1. 매크로를 포함한 expression에서 연산자 우선 순위 때문에 원하지 않은 결과를 얻을 수 있기 때문에, 항상 정의 전체를 괄호로 둘러 싸야 합니다. 예를 들어 질문에서 제공한 square() 매크로를 다음과 같이 썼다고 생각해 봅시다:
      i / square(n)
    
    그러면, 위 expression은 다음과 같이 확장됩니다:
      i / n * n
    
    따라서, (i / n) * n으로 평가됩니다. 물론 여러분이 의미한 것은 다음과 같습니다:
      i / (n * n)
    
    이 경우는, 우선 순위(precedence)라기 보다는, 결합성(associativity)에 관계된 문제입니다만, 결과는 같습니다.

  2. 매크로 정의 안에서, 모든 파라메터들은 괄호로 둘러 싸서 원치 않는 우선순위 문제를 제거합니다. 다시 한번, 질문에서 준 매크로를 다음과 같이 썼다고 가정합시다:
      square(n + 1)
    
    그러면, 위 expression은 다음과 같이 확장됩니다:
      n + 1 * n + 1
    
    즉, 다음과 같이, 우리가 원한 것과는 다른 결과가 나옵니다:
      (n + 1) * (n + 1)
    

  3. 만약에, 매크로가 확장될 때 어떤 파라메터가 여러 번 쓰였다면, 그리고 매크로에 전달한 actual argument가 side effect를 가지고 있는 것이라면, 원하는 결과를 얻을 수 없을 수도 있습니다. 예를 들어 다음과 같이 매크로를 쓰면:
      square(i++)
    
    다음과 같이 확장되기 때문에, undefined behavior를 발생시킵니다. (질문 [*]3.2 참고)
      i++ * i++
    
    즉, 규칙 1과 2에 따라서, square() 매크로는 다음과 같이 정의해야 합니다.
      #define square(x)	((x) * (x))
    

규칙 3을 지키는 것은 좀 어렵습니다. 때때로, &&||, ?: 연산자의 short-circuit 방식을 쓰면 (질문 [*]3.6 참고), 파라메터가 딱 한번만 평가되도록 할 수 있습니다. 또는 매크로가 안전하지 않으니, 부를 때, side effect가 발생하지 않도록 조심하라고 문서에 써 두는 것이 좋습니다. 또는, 안전하게 만들 수 없는 것이면, 매크로로 만들지 않는 편이 좋을 때도 있습니다.

일반적으로, (스타일에 관계되어) 매크로 이름에는 대문자를 쓰거나, 또는 전부 대문자로 써서, 매크로라는 것을 암시해 줍니다. 만약, 완전하게 함수와 같이 동작하는 것이라면, 소문자로만 이름을 만드는 것도 괜찮습니다. 그러나 이 경우, 위 세가지 규칙을 준수하는지 확인하기 바랍니다. 질문에서 제공한 매크로는 위의 세가지 규칙을 지킬 수 없으므로, 다음과 같이 만드는 것이 낫습니다:

  #define Square(x)  ((x) * (x))    /* UNSAFE */

References
[K&R1] § 4.11 p. 87
[K&R2] § 4.11.2 p. 90
[H&S] §§ 3.3.6, 3.3.7 pp. 49-50
[CT&P] § 6.2 pp. 78-80



Q 10.2
다음과 같은 매크로를 만들면, C 코드를 Pascal처럼 쓸 수 있습니다.
  #define begin    {
  #define end      }
Answer
이런 식으로 매크로를 쓰는 것은, 때때로 아주 매력적으로 보이기는 하지만, 권장되지 않는 방법입니다; 대부분의 경우, 이렇게 쓰는 것을 ``preprocessor abuse''라고 (남용) 부릅니다. Your predilections are unlikely to be shared by later readers or maintainers of the code, and any simulation of another language is most unlikely to be perfect (so any alleged convenience or utility will probably be outweighed by the nuisance of remembering the imperfections).

일반적으로, 프리프로세서를 써서 만드는 매크로는 C 언어 형식을 따르는 것이 좋습니다. 인자가 없는 매크로는 변수나 다른 identifier처럼 보이게 만들어야 하며, 인자가 있는 매크로는 함수처럼 만들어야 합니다. ``만약 프리프로세서를 거치지 않고, 이 코드를 직접 컴파일러에 주었을 때, 문법 에러가 (syntax error) 몇 개나 발생할까?''라고 자신에게 묻는 습관을 가지면 좋습니다. (물론 undefined symbol, non-constant array dimension에 관한 많은 에러가 발생하겠지만, 이들은 문법 에러가 아닙니다.) 즉, C 코드는, 매크로 호출을 거치기 전에도, C 코드처럼 보여야 합니다. begin, end, 또는 CTRL(D)와 (질문 [*]10.21 참고) 같은, nonsyntactic macro는 C 코드를 gobbledygook10.1처럼 보이게 만듭니다. 물론 이런 것은 스타일에 관한 것입니다. (Chapter 17 참고)



Q 10.3
두 변수의 값을 바꾸기 위한 일반적인 매크로를 만들 수 있을까요?
Answer
이 질문에 대한 좋은 답변은 없습니다. 변수가 정수형일때에는 잘 알려진 exclusive OR를 쓰는 방법이 있긴 합니다만 타입이 포인터나 실수(floating-point)일 때에는 동작하지 않으며, 만약 두 값이 같은 변수에 저장되어 있을 때에도 동작하지 않습니다. (또, 정수 타입에 쓸 수 있는, 아주 압축된 코드인 a ^= b ^= a ^= b는 중복된 side effect를 발생시키므로 나쁜 코드입니다. 질문 [*]3.2, [*]3.3b, [*]20.15c 참고) 만약, 모든 타입에 대해 쓸 수 있는 매크로를 만든다고 하면, 임시 변수가 반드시 필요하며, 분명 문제가 있는 코드가 됩니다. 왜냐하면:
  • 다른 이름과 절대로 충돌하지 않는다고 보장할 수 있는 임시 변수를 만들 수 없습니다. 즉, 어떤 이름을 선택하더라도, 결국 이 이름과 같은 변수가 이미 존재할 확률이 있으며, 따라서 그 변수의 값이 바뀌어 버리는 경우가 생길 수 있습니다. 만약 ## 연산자를, 두 인자에 써서, 이름을 만들어 낸다고 해도, 합쳐서 만든 이름이 31 글자를 넘는다면10.2, 다른 이름과 겹치지 않는다고 보장할 수 없습니다. 또한 간단한 이름이 아닌, 예를 들어 a[i]와 같은 인자가 들어왔을 때에는 제대로 동작하지 않을 것입니다. 질문 [*]1.29에서 다룬 것처럼, __tmp와 같은 이름을 써서, 사용자나 컴파일러 namespace에서 (완벽히 보장할 수는 없지만) 쓰이지 않는 이름을 만들 수도 있습니다.
  • 또, 임시 변수를 정확한 타입으로 선언할 방법이 없습니다. (C 표준에서는 typeof와 같은 연산자를 제공하지 않습니다.) 혹은, 적당한 크기의 메모리 공간을 만들고 그 곳에 memcpysizeof 연산자를 써서 바이트 단위로 복사하는 방식을 쓸 수도 있으나, 만약 매크로에 전달된 인자가 register로 선언되었다면 이 방법도 불가능합니다.

가장 좋은 해결책은, 두 변수의 타입을 매크로 인자로 전달하지 않는 한, 위와 같은 매크로로 처리하겠다는 생각을 접는 것입니다. (또, 만약에 어떤 structure나 배열을 바꾸겠다는 생각을 했다면, 단순히 이들을 가리키는 포인터의 값을 바꾸는 것으로 해결할 수 있습니다.)

다시 말하지만, 두 변수의 내용을 매크로를 써서 바꾸겠다는 생각에 사로잡혀 있다면, 재고하기 바랍니다. 당신의 에너지를 소비할 가치가 있는 다른 일들도 많이 널려 있습니다.



Q 10.4
여러 문장으로 이루어진 매크로를 만드는 좋은 방법 좀 알려 주세요.
Answer
이런 매크로를 만드는 일반적인 방법은 매크로 자체를 일반 함수처럼 쓸 수 있도록 하는 것입니다. 즉 호출하는 쪽에서 마지막 `;'을 직접 써 주게 하고 매크로의 몸통에서는 `;'을 따로 써 주지 않는 식으로 쓰는 것입니다. 예를 들면:
  MACRO(arg1, arg2);
그러므로 매크로 정의는 단순히 여러 statement를 중괄호로 둘러싼 `compound statement' 형식으로 만들 수 없습니다. 왜냐하면 이 매크로가 호출될 때 세미콜론이 추가적으로 붙는다면 ifif/else 문장에서 에러가 발생할 수 있기 때문입니다. 다음 코드를 보기 바랍니다:
  if (cond)
    MACRO(arg1, arg2);
  else  /* some other code */
만약, 단순히 중괄호로 둘러싼 것으로 매크로가 확장된다면, 위 코드는 다음과 같이 해석되어, 마지막 세미콜론 때문에 syntax error가 발생합니다:
  if (cond)
    { stmt1; stmt2; };
  else  /* some other code */
전통적으로, 이 경우에는 다음과 같은 방법을 씁니다:
  #define MACRO(arg1, arg2) do { \
      /* declarations */         \
      stmt1;                     \
      stmt2;                     \
      /* ...  */                 \
    } while(0) /* (no trailing ; ) */
매크로를 부르는 쪽이 세미콜론을 붙이면, 매크로가 하나의 문장으로 해석됩니다. (좋은 최적화를 제공하는 컴파일러라면 필요없는 테스트10.3나, 조건문에서 상수 0을 검사하는 일10.4같은 것은 알아서 없애 줍니다. 그러나 lint는 불평할 수 있습니다.)

또 다른 방법으로 다음과 같이 할 수도 있습니다:

  #define MACRO(arg1, arg2) if (1) \
      stmt1;                       \
      stmt2;                       \
    } else
그러나, 이 경우, 만약에 부르는 쪽에서 세미콜론을 빼먹을 경우, 전체 코드가 엉망이 되 버릴 경우가 있어서 좋은 방법이 아닙니다.

매크로의 모든 문장이 매우 간단한 expression이라면, 즉 선언이나 루프가 없다면, 콤마(`,') 연산자를 써서 한 문장으로 만들 수 있습니다:

  #define FUNC(arg1, arg2)   (expr1, expr2, expr3)
예를 들어 질문 [*]10.26의 DEBUG() 매크로를 보기 바랍니다. 이 테크닉은 또 매크로가 어떤 `값(value)'을 리턴할 수 있게 해 줍니다.

gcc와 같은 컴파일러는 이런 간단한 기능을 하는 함수가 매크로처럼 원하는 경우 확장(expand)할 수 있는 기능을, 비표준인, ``inline'' 키워드나 기타 방법을 써서 제공하기도 합니다.

Note
무슨 소리냐 하면 매크로를 정의할 때, 마지막에 세미콜론을 붙여 버린다면 아래와 같은 상황에서 에러가 발생할 수도 있다는 뜻입니다:
  #define MULTI_STATEMNT_MACRO(x)	do { \
      stmt1; \
      stmt2; \
    } while(0);

  if (some_condition)
    MULTI_STATEMNT_MACRO(a);
  else {
    /* ...  */
  }

매크로를 확장해보면 세미콜론이 두 번 만들어지고, 따라서 else 부분에서 에러가 발생합니다.

References
[H&S] § 3.3.2 p. 45
[CT&P] § 6.3 pp. 82-3
Note
``inline''은 C99에서 표준이 되었습니다.

References



Q 10.5
새로 만든 user-defined 타입을 typedef로 만드는 것이 좋을까요, 아니면, preprocessor macro로 만드는 것이 좋을까요?
Answer
질문 [*]1.13을 보기 바랍니다.



Q 10.6
여러 개의 소스 파일로 이루어진 프로그램을 만들었습니다. 그런데, 어떤 것들을 .c 파일에 두어야 하고, 어떤 것들을 .h 파일에 두어야 하는지를 모르겠습니다. (또 ``.h''는 어디에 쓰이나요?)

Answer
보통 헤더 (.h) 파일에 넣는 것들은 다음과 같습니다:

  • 매크로 정의 (#define)
  • structure, union, enum 선언
  • typedef 선언
  • 외부(external) 함수 선언 (질문 [*]1.11 참고)
  • 전역(global) 변수 선언
특히 여러 파일에서 공통적으로 쓰이는 선언이나 정의는 꼭 헤더 파일에 넣는 것이 중요합니다. 공통적으로 쓰이는 이름을 여러 소스 파일에 중복해서 선언하거나 정의하지 말기 바랍니다; 이런 부분들은 헤더 파일에 집어 넣고, #include를 써서 포함해야 합니다. 단순히 타이핑하는 수고를 덜기 위해 이러한 규칙을 정한 것이 아닙니다. 만약 공통적인 부분을 나중에 고쳐야 한다면, 한 파일을 고쳐서, 이 변경 사항이 모든 소스 파일에 반영되도록 해야 하기 때문입니다. (특히, 절대로 외부 함수 prototype을 .c에 넣어서는 안됩니다. 질문 [*]1.7을 참고하기 바랍니다.)

또, 정의나 선언이 하나의 .c 파일에서만 쓰인다면, 그 파일에 두어도 좋습니다. (그리고 이러한 private file-scope 함수나 변수들은 static으로 선언되어야 합니다. 덧붙여 질문 [*]2.4도 참고하시기 바랍니다.)

마지막으로, 실제 코드(actual code)나 (예를 들어, 함수의 몸체), 전역 변수 정의를 (정의 또는 초기화하는 코드) 헤더 파일에 두면 안됩니다. 또, 여러 소스 파일에서부터 프로젝트를 설계했다면, 각각의 소스 파일을 따로 컴파일하고 (대개 컴파일러 옵션을 써서 컴파일만 하게 할 수 있습니다) 마지막으로 모든 오브젝트 파일을 링크해야 합니다. (일반적으로 통합 환경(IDE, integrated development environment)에서는 이 모든 작업이 자동으로 진행됩니다.) Don't try to ``link'' all of your source files together with #include; the #include directive should be used to pull in header files, not other .c files.

덧붙여 질문 [*]1.7, [*]10.7, [*]17.2도 참고하시기 바랍니다.

References
[K&R2] § 4.5 pp. 81-2
[H&S] § 9.2.3 p. 267
[CT&P] § 4.6 pp. 66-7



Q 10.7
헤더 파일에서 다른 파일을 #include 하는 것은 괜찮나요?
Answer
스타일에 관한 질문이군요. 따라서 상당한 논란의 여지가 있습니다.

많은 사람들이 ``중첩된(nested) #include 파일''을 쓰지 않는 것이 좋다고 말합니다: 권위있는 Indian Hill Style Guide에서도 (질문 [*]17.9 참고) 이런 쓰임새를 피하라고 씌여있습니다; 관련된 정의를 찾기가 훨씬 더 어렵기 때문입니다; 또한 두번 #include하는 경우, 중복된 정의 에러가 (multiple-definition error) 발생할 가능성이 높습니다; 또 수동으로 Makefile을 만들 경우, 상당히 복잡해질 가능성이 있습니다.

그러나 헤더 파일을 중첩하여 포함할 경우, 각각의 헤더 파일을 모듈화해서(modular way), 헤더 파일에서 필요한 다른 헤더 파일을 #include함으로써 수고를 덜어줄 수 있다는 장점도 있습니다; grep과 같은 툴을 (또는 tags 파일) 사용하면 정의가 어떤 파일에 되어 있느냐에 상관없이 쉽게 찾을 수 있습니다. 다음과 같은 트릭을 쓰면, 헤더 파일이 여러 곳에서 포함되었느냐에 상관없이, 딱 한번만 포함되게(idempotent) 할 수 있습니다:

  #ifndef HFILENAME_USED
  #define HFILENAME_USED
  ...header file contents...
  #endif

(이 때, 각각의 헤더 파일에 각각 다른 매크로 이름을 사용합니다) 이 방식은 헤더 파일이 꼭 한 번만 포함되도록 해 주므로 여러 번 #include하더라도 문제가 발생하지 않습니다; 또한 자동으로 Makefile을 관리해주는 툴을 (큰 프로젝트를 관리할 때에는 꼭 필요합니다, 질문 [*]18.1 참고) 사용할 경우, 중첩된 #include를 처리해 주므로 좀 더 편합니다. 질문 [*]17.10을 참고하기 바랍니다.

Note
GNU autoconf와 GNU automake는 Makefile을 관리해주는 아주 좋은 툴입니다.
References
[ANSI Rationale] § 4.1.2



Q 10.8a
#include <>#include ""에 차이가 있나요?
Answer
<> 형식은 헤더 파일이 시스템에서 제공한 것이거나 표준 헤더 파일일 경우에 사용하며, ""는 프로그래머가 제작한 헤더 파일에 사용합니다.
Note
아래 질문 [*]10.8b를 참고하기 바랍니다.



Q 10.8b
헤더 파일을 찾는 알고리즘을 알고 싶어요.
Answer
정확한 알고리즘은 구현 방법에 따라 다릅니다 (implementation-defined). (즉, 이 방법에 대해 문서화되어 있을 가능성이 높습니다. 질문 [*]11.33을 참고하기 바랍니다).

일반적으로 <> 형식으로 포함된 헤더 파일들은 하나 이상의 표준으로 지정된 디렉토리에서 찾게 됩니다. (게다가, <>로 포함한 헤더들은 꼭 파일 형식으로 저장되어 있을 필요도 없습니다.) "" 형식으로 포함된 헤더 파일은 우선 ``현재 디렉토리''에서 찾은 다음, 없을 경우, 표준으로 지정된 디렉토리에서 찾게 됩니다.

전형적으로 (특히 UNIX 컴파일러), 현재 디렉토리란 #include 를 쓴 파일이 있는 디렉토리를 말합니다. 다른 컴파일러에서는 현재 디렉토리가 컴파일러가 실행된 그 디렉토리를 의미하기도 합니다. (물론, ``현재 디렉토리''라는 개념이 없는 시스템이나, 디렉토리 자체가 없는 시스템에서는 다른 규칙이 있을 것입니다)

일반적으로, 시스템 헤더 파일이 위치할 디렉토리 목록에 사용자가 원하는 디렉토리를 추가할 수 있는 방법이 (보통 컴파일러를 실행할 때 command-line option `I'를 쓰거나, 환경 변수를 지정하는 방법으로) 제공됩니다. 좀 더 자세한 것은 컴파일러의 매뉴얼을 참고하기 바랍니다.

References
[K&R2] § A12.4 p. 231
[ANSI] § 3.8.2
[C89] § 6.8.2
[H&S] § 3.4 p. 55



Q 10.9
잘못된 것이 없어 보이는 데에도 코드의 첫번째 선언문에서 `syntax error'가 발생합니다.
Answer
아마도 마지막으로 #include한 헤더 파일의 내용에서, 마지막 선언 부분에서 세미콜론(semicolon)이 빠져 있을 가능성이 높습니다. 질문 [*]2.18, [*]11.29, [*]16.1b를 참고하기 바랍니다.



Q 10.10
다른 곳에서 제작한 라이브러리 두 개를 쓰는 헤더 파일을 만들었습니다. 그런데 이 라이브러리들이, 도움을 준답시고, 여러 매크로들을 정의했습니다. 예를 들면, TRUE, FALSE, Min(), Max() 등. 그래서 컴파일할 때, 제가 만든 헤더 파일에서 매크로가 여러번 정의되어 있다고 에러가 발생합니다. 어떻게 하면 되죠?
Answer
매우 짜증나는 상황 중 하나입니다. 전형적인 namespace problem이라 할 수 있으며, 질문 [*]1.9, [*]1.29를 참고하기 바랍니다. 이상적으로(ideally), 라이브러리 제작자는 symbol을 (예를 들어, 매크로, 전역 변수 및 함수) 정의할 때, 이름이 서로 충돌하지 않도록, 매우 조심스럽게 해야 합니다. 가장 좋은 방법은, 라이브러리 제작자에게 연락해서, 코드를 고치는 것입니다. 힘들다면, 임시적으로, 매크로 정의를 없애고(undefine), 다시 정의하는(redefine) 것을 반복해서 #include에서 일어나는 충돌을 막을 수 있습니다.
Note
서로 충돌하는 매크로의 실제 정의 부분이, 다음과 같이 서로 같다면:
  file1.h:
    #include TRUE	1
    ...
  file2.h:
    #include TRUE	1
    ...
다음과 같이 두 파일의 내용을 고쳐서 해결할 수 있습니다:
  #ifndef TRUE
  #define TRUE	1
  #endif
그러나 이 방법은 두 매크로의 내용이 완전히 같을 때 쓸 수 있습니다. 서로 내용이 다른 매크로라면, 코드가 안전하다고 보장할 수 없습니다.



Q 10.10b
라이브러리 함수 정의를 포함하는 헤더 파일을 제대로 #include시켰는데도 링커(linker)는 정의되어 있지 않다고 에러를 발생합니다.

Answer
질문 [*]13.25를 참고하기 바랍니다.



Q 10.11
시스템 헤더 파일인 <sgtty.h>가 없습니다. 제게 복사본을 주실 수는 없을까요?

Answer
표준 헤더 파일들은 컴파일러, 운영체제, 프로세서에 따라 서로 다른 정의로 이루어져 있습니다. 따라서 다른 사람의 헤더 파일을 가져온다 하더라도 제대로 동작하지 않을 것입니다. 물론 그 사람이 여러분과 완전히 같은 시스템을 사용하고 있다면 동작할 수도 있습니다. 컴파일러 vendor에게, 왜 그 헤더 파일이 제공되지 않는지 물어보고, 그 파일을 달라고 요청해 보시기 바랍니다.

표준이 아닌 헤더 파일인 경우에는 조금 더 까다롭습니다. 어떤 헤더 파일들은 (예를 들어 <dos.h>), 완전하게 컴파일러 또는 OS에 종속되어 제공됩니다. (즉, 다른 시스템에서는 원래 제공되지 않을 수 있습니다.) 이와 달리, 인기있는, 라이브러리에서 제공하는 헤더 파일이 없는 경우에는, 이 라이브러리를 얻은 곳에서 찾아보기 바랍니다. 그 헤더 파일을 쓰는 소스는 있는데, 그 헤더 파일이 없는 경우, 관련 라이브러리 자체가 설치되어 있지 않을 수도 있습니다. (질문 [*]13.25 참고) 때때로 archie가 여러분이 찾는 것에 대해 도움을 줄 수도 있습니다; 질문 [*]18.16 참고.

Note
ARCHIE는 1990년에 Alan Emtage, Bill Heelan, Peter J. Deutsch가 만든 FTP 검색 프로그램이며, 현재에는 거의 쓰이지 않습니다.

10.1 Conditional Compilation



Q 10.12
전처리기(preprocessor) #if 수식에서 문자열을 비교할 수 있을까요?

Answer
직접 비교할 수 없습니다; #if는 정수 연산만 지원합니다. 따라서, 여러 상황을 정수 상수로 정의하고 다음과 같이 할 수 있습니다:
  #define RED        1
  #define BLUE       2
  #define GREEN      3

  #if COLOR == RED
  /* red case */
  #else
  #if COLOR == BLUE
  /* blue case */
  #else
  #if COLOR == GREEN
  /* green case */
  #else
  /* default case */
  #endif
  #endif
  #endif
(C 표준에서 소개된 #elif를 쓰면 위 코드를 조금 더 깔끔하게 만들 수 있습니다.)

질문 [*]20.17을 참고하기 바랍니다.

Note
#elif를 쓰면 위 코드를 다음과 같이 쓸 수 있습니다.
  #define RED        1
  #define BLUE       2
  #define GREEN      3

  #if COLOR == RED
  /* red case */
  #elif COLOR == BLUE
  /* blue case */
  #elif COLOR == GREEN
  /* green case */
  #else
  /* default case */
  #endif

References
[K&R2] § 4.11.3 p. 91
[ANSI] § 3.8.1
[C89] § 6.8.1
[H&S] § 7.11.1 p. 225



Q 10.13
#if 지시어(directive)에서 sizeof 연산을 쓸 수 있을까요?
Answer
쓸 수 없습니다. `전처리(preprocessing)'는 말 그대로 (타입 이름을 parsing하기 전에) 컴파일 초기 단계 이전에 이루어지기 때문입니다. sizeof를 쓰는 대신 ANSI 표준인 <limits.h>에 정의되어 있는 상수를 쓰는 방법으로 바꾸기 바랍니다. 가능하다면, ``configure'' 스크립트(script)를 쓰는 것도 좋습니다. (프로그램을 타입의 크기에 독립적으로 작성하는 게 더 바람직합니다; 질문 [*]1.1을 참고하기 바랍니다.)

References
[ANSI] § 2.1.1.2, § 3.8.1 footnote 83
[C89] § 5.1.1.2, § 6.8.1
[H&S] § 7.11.1 p. 225
Note

GNU autoconf 패키지는 시스템에서 어떤 기능을 제공하는지, 어떤 타입의 크기가 얼마인지 알아내는 `configure' 스크립트를 자동으로 만들어 줍니다. 이 `configure'를 실행하면 Makefile이 만들어지므로, 사용자가 소스에서 실행 파일을 만드는 데 필요한 수고를 많이 덜어 줍니다. 이 패키지에 관한 것은 아래 URL을 참고하기 바랍니다:

  http://www.gnu.org/software/autoconf/
  ftp://ftp.gnu.org/pub/gnu/autoconf/



Q 10.14
#define 줄에서 #ifdef를 써서 다음과 같이 각각 다른 방식으로 정의하게 할 수 있을까요?
  #define a b \
  #ifdef whatever
          c d
  #else
          e f g
  #endif

Answer
안됩니다. 프리프로세서를 자신에게 또 실행하는 (run the preprocessor on itself) 것은 불가능합니다. 대신 #ifdef를 써서 두 개의 #define 문장으로 만드는 방식을 쓰기 바랍니다:
  #ifdef whatever
  #define a b c d
  #else
  #define a b e f g
  #endif

References
[ANSI] § 3.8.3, § 3.8.3.4
[C89] § 6.8.3, § 6.8.3.4
[H&S] § 3.2 pp. 40-1



Q 10.15
typedef 이름을 #ifdef와 같이 테스트할 수 있는 방법이 있을까요?

Answer
불행하게도 그런 방법은 존재하지 않습니다. (왜냐하면, typedef와 타입은 프리프로세싱할 때 준비되어 있지 않기 때문입니다.) 질문 [*]1.13, [*]10.13을 참고하기 바랍니다.

대신 이런 방법을 생각할 수 있습니다: 몇가지 매크로를 정의해서 (예: MY_TYPE_DEFINED) 어떤 typedef들이 선언되어 있는지 매크로를 써서 검사할 수 있습니다 (물론, 완전하지 않습니다).

References
[ANSI] § 2.1.1.2, § 3.8.1 footnote 83
[C89] § 5.1.1.2, § 6.8.1
[H&S] § 7.11.1 p. 225



Q 10.16
컴퓨터가 `big-endian' 방식인지, `little-endian' 방식인지 #if를 써서 검사할 수 있을까요?

Answer
거의 불가능합니다. Endian을 확인하기 위해서는 대개 포인터와 char 배열 또는 union을 써서 확인하는데, 프리프로세서 연산은 단지 long 정수만 쓰며, 주소(addressing)에 대한 개념이 프리프로세서에 존재하지 않습니다.

정말로 컴퓨터의 endian을 알아야 할 필요가 있는지 잘 생각해보기 바랍니다. 될 수 있으면, 이런 것과 무관하게 코드를 작성하는 것이 바람직합니다. (예를 들어 질문 [*]12.42에 나온 코드를 보기 바랍니다.) 덧붙여 질문 [*]20.9도 참고하시기 바랍니다.

References
[ANSI] § 3.8.1
[C89] § 6.8.1
[H&S] § 7.11.1 p. 225
Note
GNU autoconf를 쓰면 프로그램을 컴파일하기 전 간단한 인디언 테스트용 프로그램을 실행해서, 자동으로 어떤 매크로를 정의하게 만들고 (예를 들어 BIG_ENDIAN_MACHINE) 프로그래머가 이 매크로의 정의 여부에 따라 프로그램을 작성할 수 있습니다. (질문 [*]10.13 참고).



Q 10.17
분명히 #ifdef를 써서 포함되지 않도록 한 부분에서 이상한 syntax error가 생깁니다.
Answer
질문 [*]11.19를 보기 바랍니다.



Q 10.18
어떤 코드를 분석하려 하는데, 너무나도 많은 #ifdef 때문에 어렵습니다. 어떤 조건부 컴파일에 관한 것만 남겨두고 나머지 부분만 `preprocssing'할 수 있는 방법이 있을까요? (물론 #include#define은 제외한다는 조건하에서)

Answer
이러한 일을 처리해 주는 unifdef, rmifdef, scpp (``selective C preprocessor'') 등의 프로그램이 있습니다. 질문 [*]18.16을 참고하기 바랍니다.



Q 10.19
미리 정의된(predefined) 모든 매크로들을 뽑아낼 수 있을까요?

Answer
자주 언급되는 사항임에도 이런 일을 할 수 있는 표준 방법은 없습니다. 컴파일러 문서에 만족할만한 정보가 없다면, 가장 적당한 방법으로, 컴파일러 또는 프리프로세서 실행 파일에서, 파일에서 문자열을 뽑아내는 UNIX strings와 같은 프로그램을 쓰면 됩니다. gcc-E와 함께 사용할 수 있는 -dM 옵션을 제공합니다. 다른 컴파일러도 비슷한 기능을 제공할 것입니다. 많은 오래된(traditional) 시스템 전용의 predefined identifier들이 (예를 들어 ``unix'') 비표준이므로 (왜냐하면, user namespace와 충돌하기 때문), 조심하기 바랍니다. 이런 비표준인 이름들은 대개 (표준에서) 곧 없어질 예정이거나 다른 이름으로 바뀔 예정인 것들이 많습니다. (어떤 경우라도 조건부 컴파일을 최소화하는 것이 좋은 방법이라는 것을 잊지 말기 바랍니다.)

10.2 Fancier Processing

Macro replacement can get fairly complicated--sometimes too complicated. For two somewhat popular tricks that used to work (if at all) by accident, namely, ``token pasting'' and replacement inside string literals, ANSI C introduces defined, supported mechanisms.



Q 10.20
다음과 같이 `identifier'를 생성하는 매크로를 본 적이 있습니다:
  #define Paste(a, b) a/**/b
그러나 동작하질 않는군요.

Answer
(특히 John Reiser의) 오래된 전처리기에서는, 주석(comment)을 봤을 때, 주석 자체를 없애기 때문에, 주석 양 옆의 토큰(token)을 서로 붙이는데 (pasting) 씁니다. 그러나, ANSI 표준에서는 ([K&R1] 포함), Comment가 공백으로 대체된다고 씌여있기 때문에, 더 이상 쓸 수 없는 기능입니다. 따라서 위 Paste() 매크로는 쓸 수 없습니다. 대신, 토큰을 서로 붙이는 기능 자체는 필요하기 때문에, ANSI는 이 역할을 하는 연산자인 ##를 제공합니다. 따라서 위 매크로는 다음과 같이 만들 수 있습니다:
  #define Paste(a, b) a##b

참고로 ANSI 이전의 컴파일러에서 쓸 수 있는 또다른 방법은 다음과 같습니다:

  #define XPaste(s)   s
  #define Paste(a, b) XPaste(a)b
질문 [*]11.17을 참고하기 바랍니다.

References
[ANSI] § 3.8.3.3
[C89] § 6.8.3.3
[ANSI Rationale] § 3.8.3.3
[H&S] § 3.3.9 p. 52



Q 10.21
어떤 코드에는 다음과 같은 매크로를 쓰는데:
  #define CTRL(c)   ('c' & 037)
동작하지 않습니다. 왜 그럴까요?
Answer
위 매크로는 보통 다음과 같이 쓰기 위해 만들어졌습니다:
  tchars.t_eofc = CTRL(D);
다음과 같이 확장될 것을 예상하고 만든 것입니다:
  tchars.t_eofc = ('D' & 037);
결국, 이 매크로는 파라메터 c가 문자 상수에 쓰이는 따옴표 안에서도 확장될 것을 예상하고 만든 것인데, 프리프로세서는 이런 경우에 확장하지 않습니다; 이 코드가 제대로 동작했다면, 그 자체가 잘못된 것입니다. ANSI C는 이런 목적으로 쓸 수 있는, 문자열로 만들어주는(stringizing) 연산자를 제공합니다 (질문 [*]11.17 참고). 그러나 문자로 만들어주는 (charizing) 연산자는 제공하지 않습니다.

이 매크로를 해결하는 가장 좋은 방법은 다음과 같이 매크로를 만들고:

  #define CTRL(c)   (c & 037)
다음과 같이 호출하는 것입니다:
  CTRL('D')
이렇게 하면, 매크로를 C 언어 형식으로 만들어주는 (``syntactic'') 효과도 있습니다; 질문 [*]10.2 참고.

Stringizing 연산자와 약간의 indirection을 써서 만들 수도 있습니다:

  #define CTRL(c)    (*#c & 037)
또는
  #define CTRL(c)    (#c[0] & 037)
위 두가지 방법 모두 완전하다고 볼 수 없습니다. 위 두 매크로 모두, case 레이블이나, 전역 변수를 초기화할 때 쓰일 수 없습니다. (전연 변수 초기화에 쓰이거나, case 레이블로 쓰이려면, 여러가지 형태의 상수 expression이 필요하며, 문자열이나 indirection이 허용되지 않습니다.)

덧붙여 질문 [*]11.18도 참고하시기 바랍니다.

Note
``Stringizing''이란 말 대신, ``Stringification''이란 말을 쓰기도 합니다만, 아시다시피 둘 다 올바른 영어 단어는 아닙니다.
References
[ANSI] § 3.8.3 footnote 87
[C89] § 6.8.3
[H&S] §§ 7.11.2, 7.11.3 pp. 226-7



Q 10.22
다음 매크로를 쓰면 ``macro replacement within a string literal?'' 이라는 경고가 발생합니다:
  #define TRACE(n) printf("TRACE: %d\n", n)
제가 생각하기에는 TRACE(count);를 다음과 같이 확장하는 것 같습니다:
  printf("TRACE: %d\count", count);

Answer
질문 [*]11.18을 참고하기 바랍니다.



Q 10.23
매크로를 확장할 때, 문자열 안에까지 매크로 인자가 확장되게 할 수 있을까요?
Answer
질문 [*]11.18을 보기 바랍니다.



Q 10.24
ANSI ``stringing'' 프리프로세싱 연산자인 `#'를 써서 매크로 값을 메시지에 넣을려고 했는데, 매크로의 값이 아닌, 매크로의 이름을 문자열로 만들어 버립니다. 왜 그럴까요?
Answer
질문 [*]11.17과 [*]11.18을 참고하기 바랍니다.



Q 10.25
복잡한 전처리를 해야 하는데, 방법을 모르겠습니다.

Answer
C 언어의 전처리기(preprocessor)는 범용의(general purpose) 툴이 아닙니다. (또한 전처리기가 독립적인 프로그램으로 제공되는지도 알 수 없습니다.) 이를 복잡한 형식으로 사용하기에 앞서, 어떤 특정한 목적의 작은 프리프로세싱 툴을 직접 만드는 것이 나을 지 생각해 보기 바랍니다. make과 같은 툴을 사용하면, 이러한 프리프로세싱 툴을 자동으로 실행하도록 만들 수 있습니다.

C 언어 대신 다른 것을 `preprocessing' 하려고 한다면 여러가지 목적으로 쓸 수 있는 프리프로세서를 쓰기 바랍니다. (오래 전부터, 대부분의 UNIX 시스템에서는 `m4'라는 프리프로세서를 제공합니다.)

10.3 Macros with
Variable-Length Argument Lists

There are sometimes good reasons for a function to accept a variable number of arguments (the canonical example is printf; see also Chapter 15). For the same sorts of reasons, it's sometimes wished that a function-like macro could accept a variable number of arguments; a particularly common wish is for a way to write a general-purpose printf-like DEBUG() macro.



Q 10.26
가변 인자를 받는 매크로를 만들 수 있습니까?

Answer
각각의 인자를 하나의 괄호로 둘러싸서 하나의 인자처럼 전달하는 것은 매우 인기있는 트릭입니다. 다음과 같이 할 수 있습니다:
  #define DEBUG(args) (printf("DEBUG: "), printf args)

  if (n != 0) DEBUG(("n is %d\n", n));
문제는 이런 매크로 함수를 호출할 때, 항상 괄호를 두 쌍을 써 줘야 한다는 것을 기억해야 합니다. 또 다른 문제는, 추가적으로 다른 인자를 줄 수 없다는 것입니다. (즉, DEBUG()fprintf(debugfd, ...)처럼 확장될 수 없습니다.)

GCC는 함수처럼 가변 인자를 받을 수 있는 확장 매크로를 지원하지만 이 기능은 표준이 아닙니다. 생각해 볼 수 있는 다른 방법으로, 다음과 같은 방법들이 있습니다:

  • 인자의 갯수에 따라 다른 매크로를 (DEBUG1, DEBUG2 같이) 제공합니다.
  • 콤마(comma)를 별도의 매크로를 써서 제공하는 방법이 있습니다:
      #define DEBUG(args) (printf("DEBUG: "), printf(args))
      #define _ ,
    
      DEBUG("i = %d" _ i)
    
  • 위험하지만, 다음과 같이, 쌍이 맞지 않는 괄호를 쓸 수 있습니다:
      #define DEBUG       fprintf(stderr,
    
      DEBUG "%d", x);
    
여기에 나온 모든 방식이, 조심해서 써야 하며, 보기에 좋지 않는 방법입니다.

보통은 매크로가 아닌, 진짜 함수를 써서 가변 인자를 처리하는 것이 낫습니다. 질문 [*]15.4, [*]15.5를 보기 바랍니다.

디버그 관련 메시지가 출력되지 않게 하려면, 따로 DEBUG 매크로의 다른 버전을 다음과 같이 만듭니다:

  #define DEBUG(args)  /* empty */

또는, 진짜 함수를 쓴다면, 인자는 그대로 두고, 함수 이름만 없애버릴 수 있습니다:

  #define DEBUG      (void)
또는,
  #define DEBUG      if (1) {} else printf
또는,
  #define DEBUG      1 ? 0 : (void)
이 모든 방법은, 좋은 optimizer가 있어서, 의미없는 ``printf'' 호출을 제거하고, void 타입으로 캐스팅 된, 컴마 연산자의 expression을 코드로 만들지 않는다는 것을 가정하고 만든 것입니다. 질문 [*]10.14를 참고하기 바랍니다.

C99는 가변 인자를 처리할 수 있는 함수 매크로를 소개하고 있습니다. ... 형식을 매크로의 끝에 사용하고 (varargs 함수처럼), 매크로 함수 안에서 __VA_ARGS__ pseudo 매크로를 써서 가변 인자를 처리할 수 있습니다.

References
[C89] § 6.8.3, § 6.8.3.1
Note
C99 표준을 써서, 정말 좋은 형태의 DEBUG 매크로를 다음과 같이 만들 수 있습니다:
  #define DEBUG(...) fprintf(debugfd, __VA_ARGS__)

  DEBUG("age = %d, name = %s\n", age, namestr);
가변 인자를 받는 함수와 달리, 고정 인자가 하나 이상 필요하다는 규칙은 적용되지 않습니다.
References



Q 10.27
__FILE____LINE__ 매크로를, 디버그용으로, 메시지 출력하는 매크로에 쓰고 싶습니다. 어떻게 하면 될까요?
Answer
질문 [*]10.26을 간단히 하기 위해, 따로 만든 질문입니다. 한가지 방법은, 가변 인자를 받는 함수를 만들고 (질문 [*]15.4, [*]15.5 참고), __FILE____LINE__을 받아서 그 함수에 전달해 주는 함수를 따로 만듭니다. 예를 들면:
  #include <stdio.h>
  #include <stdarg.h>

  void debug(char *fmt, ...);
  void dbginfo(int, char *);
  #define DEBUG       dbginfo(__LINE__, __FILE__), debug

  static char *dbgfile;
  static int dbgline;

  void dbginfo(int line, char *file)
  {
    dbgfile = file;
    dbgline = line;
  }

  void debug(char *fmt, ...)
  {
    va_list argp;
    fprintf(stderr, "DEBUG: \"%s\", line %d: ",
            dbgfile, dbgline);
    va_start(argp, fmt);
    vfprintf(stderr, fmt, argp);
    va_end(argp);
    fprintf(stderr, "\n");
  }
이렇게 만들어 두고, 다음과 같이 부르면:
  DEBUG("i is %d", i);
다음과 같이 확장됩니다:
  dbginfo(__LINE__, __FILE__), debug("i is %d", i);
그리고 아래 문장을 출력합니다:
  DEBUG: "x.c", line 10: i is 42

영리한 사람이면 다음과 같이 위 코드를 개선할 수 있습니다:

  void debug(char *fmt, ...);
  void (*dbginfo(int, char *))(char *, ...);
  #define DEBUG  (*dbginfo(__LINE__, __FILE__))

  void (*dbginfo(int line, char *file))(char *, ...)
  {
    dbgfile = file;
    dbgline = line;
    return debug;
  }
이 정의에 따라서 DEBUG("i is %d", i);는 다음과 같이 확장됩니다:
  (*dbginfo(__LINE__, __FILE__))("i is %d", i);

또 다른, 그리고 간단한 방법으로 아래처럼 만들 수 있습니다:

  #define DEBUG   printf("DEBUG: \"%s\", line %d: ", \
                         __FILE__, __LINE__), printf
그러면, DEBUG("i is %d", i);는 다음과 같이 확장됩니다:
  printf("DEBUG: \"%s\", line %d: ",
         __FILE__, __LINE__), printf("i is %d", i);

Note
다행스럽게도, C99에서 가변 인자를 받을 수 있는 매크로를 지원하기 때문에 (질문 [*]10.26 참고), DEBUG() 매크로를 다음과 같이 만들 수 있습니다.
  #define DEBUG(...)	debug(__FILE__, __LINE__, __VA_ARGS__)

  void debug(const char *file, int line,
             const char *fmt, ...)
  {
    va_list argp;
    fprintf(stderr, "DEBUG: \"%s\", line %d: ",
            file, line);
    va_start(argp, fmt);
    vfprintf(stderr, fmt, argp);
    va_end(argp);
    fprintf(stderr, "\n");
  }
위 매크로에 따르면, DEBUG("i is %d", i);는 다음과 같이 확장됩니다:
  debug(__FILE__, __LINE__, "i is %d", i);

C99에서는 미리 정의된(predefined) identifier인 __func__를 제공합니다. (이 것은 __FILE__, __LINE__과는 달리 미리 정의된 매크로가 아닙니다. 자세한 것은 ???를 참고하기 바랍니다.) __func__는 현재 함수의 이름을 가지고 있는 문자열 상수라고 생각하시면 됩니다. 따라서 위 매크로와 함수를 다음과 같이 만들면 더욱 좋습니다:

  #define DEBUG(...)	debug(__FILE__, __LINE__, __func__, \
                           __VA_ARGS__)

  void debug(const char *file, int line, const char *func,
             const char *fmt, ...)
  {
    va_list argp;
    fprintf(stderr, 
            "DEBUG: \"%s\", line %d, function \"%s\": ",
            file, line, func);
    va_start(argp, fmt);
    vfprintf(stderr, fmt, argp);
    va_end(argp);
    fprintf(stderr, "\n");
  }
위 매크로에 따르면, DEBUG("i is %d", i);는 다음과 같이 확장됩니다:
  debug(__FILE__, __LINE__, __func__, "i is %d", i);

References
[C89] ???


next up previous contents
Next: 11. ANSI/ISO Standard C Up: C Programming FAQs Previous: 9. Boolean Expressions and Variables

All rights reserved. Copyright © 2004-2006 Seong-Kook Shin (신성국)
Return to my homepage


2006-08-30