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.
#define square(x) x * x그런데, 가끔씩 제대로 동작하지 않습니다. 왜 그럴까요?
i / square(n)그러면, 위 expression은 다음과 같이 확장됩니다:
i / n * n따라서, (i / n) * n으로 평가됩니다. 물론 여러분이 의미한 것은 다음과 같습니다:
i / (n * n)이 경우는, 우선 순위(precedence)라기 보다는, 결합성(associativity)에 관계된 문제입니다만, 결과는 같습니다.
square(n + 1)그러면, 위 expression은 다음과 같이 확장됩니다:
n + 1 * n + 1즉, 다음과 같이, 우리가 원한 것과는 다른 결과가 나옵니다:
(n + 1) * (n + 1)
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 */
#define begin { #define end }
일반적으로, 프리프로세서를 써서 만드는 매크로는 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 참고)
a ^= b ^= a ^= b
는 중복된 side effect를 발생시키므로
나쁜 코드입니다. 질문
3.2,
3.3b,
20.15c 참고)
만약, 모든 타입에 대해 쓸 수 있는
매크로를 만든다고 하면, 임시 변수가 반드시 필요하며, 분명 문제가
있는 코드가 됩니다. 왜냐하면:
##
연산자를, 두 인자에 써서, 이름을 만들어 낸다고 해도, 합쳐서
만든 이름이 31 글자를 넘는다면10.2, 다른
이름과 겹치지 않는다고 보장할 수 없습니다. 또한 간단한 이름이
아닌, 예를 들어 a[i]와 같은 인자가 들어왔을 때에는 제대로
동작하지 않을 것입니다. 질문
1.29에서 다룬 것처럼, __tmp
와
같은 이름을 써서, 사용자나 컴파일러 namespace에서 (완벽히 보장할
수는 없지만) 쓰이지 않는 이름을 만들 수도 있습니다.
가장 좋은 해결책은, 두 변수의 타입을 매크로 인자로 전달하지 않는 한, 위와 같은 매크로로 처리하겠다는 생각을 접는 것입니다. (또, 만약에 어떤 structure나 배열을 바꾸겠다는 생각을 했다면, 단순히 이들을 가리키는 포인터의 값을 바꾸는 것으로 해결할 수 있습니다.)
다시 말하지만, 두 변수의 내용을 매크로를 써서 바꾸겠다는 생각에 사로잡혀 있다면, 재고하기 바랍니다. 당신의 에너지를 소비할 가치가 있는 다른 일들도 많이 널려 있습니다.
MACRO(arg1, arg2);그러므로 매크로 정의는 단순히 여러 statement를 중괄호로 둘러싼 `compound statement' 형식으로 만들 수 없습니다. 왜냐하면 이 매크로가 호출될 때 세미콜론이 추가적으로 붙는다면 if나 if/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” 키워드나 기타 방법을 써서 제공하기도 합니다.
#define MULTI_STATEMNT_MACRO(x) do { \ stmt1; \ stmt2; \ } while(0); if (some_condition) MULTI_STATEMNT_MACRO(a); else { /* ... */ }
매크로를 확장해보면 세미콜론이 두 번 만들어지고, 따라서 else 부분에서 에러가 발생합니다.
.c
파일에 두어야 하고, 어떤 것들을
.h
파일에 두어야 하는지를 모르겠습니다. (또 “.h
”는
어디에 쓰이나요?)
.h
) 파일에 넣는 것들은 다음과 같습니다:
#define
)
#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.
#include
하는 것은
괜찮나요?
많은 사람들이 “중첩된(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을 참고하기 바랍니다.
#include <>
와 #include ""
에 차이가 있나요?
<>
형식은 헤더 파일이 시스템에서 제공한 것이거나
표준 헤더 파일일 경우에 사용하며, ""
는 프로그래머가 제작한
헤더 파일에 사용합니다.
일반적으로 <>
형식으로 포함된 헤더 파일들은 하나 이상의
표준으로 지정된 디렉토리에서 찾게 됩니다. (게다가,
<>
로 포함한 헤더들은 꼭 파일 형식으로 저장되어 있을 필요도
없습니다.) ""
형식으로 포함된
헤더 파일은 우선 “현재 디렉토리”에서 찾은 다음, 없을 경우,
표준으로 지정된 디렉토리에서 찾게 됩니다.
전형적으로 (특히 UNIX 컴파일러), 현재 디렉토리란 #include
를 쓴 파일이 있는 디렉토리를 말합니다. 다른 컴파일러에서는
현재 디렉토리가 컴파일러가 실행된 그 디렉토리를 의미하기도 합니다.
(물론, “현재 디렉토리”라는 개념이 없는 시스템이나, 디렉토리 자체가
없는 시스템에서는 다른 규칙이 있을 것입니다)
일반적으로, 시스템 헤더 파일이 위치할 디렉토리 목록에 사용자가 원하는 디렉토리를 추가할 수 있는 방법이 (보통 컴파일러를 실행할 때 command-line option `I'를 쓰거나, 환경 변수를 지정하는 방법으로) 제공됩니다. 좀 더 자세한 것은 컴파일러의 매뉴얼을 참고하기 바랍니다.
#include
한 헤더 파일의 내용에서,
마지막 선언 부분에서
세미콜론(semicolon)이 빠져 있을 가능성이 높습니다.
질문
2.18,
11.29,
16.1b를 참고하기 바랍니다.
#include
에서 일어나는 충돌을 막을 수 있습니다.
file1.h: #include TRUE 1 ... file2.h: #include TRUE 1 ...다음과 같이 두 파일의 내용을 고쳐서 해결할 수 있습니다:
#ifndef TRUE #define TRUE 1 #endif그러나 이 방법은 두 매크로의 내용이 완전히 같을 때 쓸 수 있습니다. 서로 내용이 다른 매크로라면, 코드가 안전하다고 보장할 수 없습니다.
#include
시켰는데도 링커(linker)는 정의되어 있지 않다고
에러를 발생합니다.
<sgtty.h>
가 없습니다.
제게 복사본을 주실 수는 없을까요?
표준이 아닌 헤더 파일인 경우에는 조금 더 까다롭습니다. 어떤 헤더 파일들은 (예를 들어 <dos.h>), 완전하게 컴파일러 또는 OS에 종속되어 제공됩니다. (즉, 다른 시스템에서는 원래 제공되지 않을 수 있습니다.) 이와 달리, 인기있는, 라이브러리에서 제공하는 헤더 파일이 없는 경우에는, 이 라이브러리를 얻은 곳에서 찾아보기 바랍니다. 그 헤더 파일을 쓰는 소스는 있는데, 그 헤더 파일이 없는 경우, 관련 라이브러리 자체가 설치되어 있지 않을 수도 있습니다. (질문 13.25 참고) 때때로 archie가 여러분이 찾는 것에 대해 도움을 줄 수도 있습니다; 질문 18.16 참고.
#if
수식에서 문자열을
비교할 수 있을까요?
#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
를 쓰면 위 코드를 조금 더
깔끔하게 만들 수 있습니다.)
#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
#if
지시어(directive)에서 sizeof 연산을
쓸 수 있을까요?
<limits.h>
에
정의되어 있는 상수를 쓰는 방법으로 바꾸기 바랍니다.
가능하다면, “configure” 스크립트(script)를 쓰는 것도 좋습니다.
(프로그램을 타입의 크기에 독립적으로 작성하는 게 더 바람직합니다;
질문
1.1을 참고하기 바랍니다.)
GNU autoconf 패키지는 시스템에서 어떤 기능을 제공하는지, 어떤 타입의 크기가 얼마인지 알아내는 `configure' 스크립트를 자동으로 만들어 줍니다. 이 `configure'를 실행하면 Makefile이 만들어지므로, 사용자가 소스에서 실행 파일을 만드는 데 필요한 수고를 많이 덜어 줍니다. 이 패키지에 관한 것은 아래 URL을 참고하기 바랍니다:
http://www.gnu.org/software/autoconf/ ftp://ftp.gnu.org/pub/gnu/autoconf/
#define
줄에서 #ifdef
를 써서 다음과 같이
각각 다른 방식으로 정의하게 할 수 있을까요?
#define a b \ #ifdef whatever c d #else e f g #endif
#ifdef
를 써서 두 개의 #define
문장으로 만드는 방식을 쓰기 바랍니다:
#ifdef whatever #define a b c d #else #define a b e f g #endif
typedef
이름을 #ifdef
와 같이 테스트할 수 있는
방법이 있을까요?
대신 이런 방법을 생각할 수 있습니다:
몇가지 매크로를 정의해서 (예: MY_TYPE_DEFINED
)
어떤 typedef들이 선언되어 있는지 매크로를 써서
검사할 수 있습니다 (물론, 완전하지 않습니다).
#if
를 써서 검사할 수 있을까요?
정말로 컴퓨터의 endian을 알아야 할 필요가 있는지 잘 생각해보기 바랍니다. 될 수 있으면, 이런 것과 무관하게 코드를 작성하는 것이 바람직합니다. (예를 들어 질문 12.42에 나온 코드를 보기 바랍니다.) 덧붙여 질문 20.9도 참고하시기 바랍니다.
BIG_ENDIAN_MACHINE
)
프로그래머가 이 매크로의 정의 여부에 따라 프로그램을 작성할
수 있습니다. (질문
10.13 참고).
#ifdef
를 써서 포함되지 않도록 한 부분에서 이상한
syntax error가 생깁니다.
#ifdef
때문에
어렵습니다. 어떤 조건부 컴파일에 관한 것만 남겨두고 나머지
부분만 `preprocssing'할 수 있는 방법이 있을까요? (물론
#include
나 #define
은 제외한다는 조건하에서)
-E
와 함께 사용할 수 있는 -dM
옵션을
제공합니다. 다른 컴파일러도 비슷한 기능을 제공할 것입니다.
많은
오래된(traditional) 시스템 전용의 predefined identifier들이 (예를 들어
“unix”) 비표준이므로 (왜냐하면, user namespace와 충돌하기 때문),
조심하기 바랍니다. 이런 비표준인 이름들은 대개 (표준에서) 곧 없어질
예정이거나 다른 이름으로 바뀔 예정인 것들이 많습니다. (어떤 경우라도
조건부 컴파일을 최소화하는 것이 좋은 방법이라는 것을 잊지 말기
바랍니다.)
#define Paste(a, b) a/**/b그러나 동작하질 않는군요.
##
를 제공합니다. 따라서
위 매크로는 다음과 같이 만들 수 있습니다:
#define Paste(a, b) a##b
참고로 ANSI 이전의 컴파일러에서 쓸 수 있는 또다른 방법은 다음과 같습니다:
#define XPaste(s) s #define Paste(a, b) XPaste(a)b질문 11.17을 참고하기 바랍니다.
#define CTRL(c) ('c' & 037)동작하지 않습니다. 왜 그럴까요?
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이 허용되지 않습니다.)
#define TRACE(n) printf("TRACE: %d\n", n)제가 생각하기에는 TRACE(count);를 다음과 같이 확장하는 것 같습니다:
printf("TRACE: %d\count", count);
#
'를 써서
매크로 값을 메시지에 넣을려고 했는데, 매크로의 값이 아닌, 매크로의
이름을 문자열로 만들어 버립니다. 왜 그럴까요?
C 언어 대신 다른 것을 `preprocessing' 하려고 한다면 여러가지 목적으로 쓸 수 있는 프리프로세서를 쓰기 바랍니다. (오래 전부터, 대부분의 UNIX 시스템에서는 `m4'라는 프리프로세서를 제공합니다.)
#define DEBUG(args) (printf("DEBUG: "), printf args) if (n != 0) DEBUG(("n is %d\n", n));문제는 이런 매크로 함수를 호출할 때, 항상 괄호를 두 쌍을 써 줘야 한다는 것을 기억해야 합니다. 또 다른 문제는, 추가적으로 다른 인자를 줄 수 없다는 것입니다. (즉, DEBUG()는 fprintf(debugfd, ...)처럼 확장될 수 없습니다.)
GCC는 함수처럼 가변 인자를 받을 수 있는 확장 매크로를 지원하지만 이 기능은 표준이 아닙니다. 생각해 볼 수 있는 다른 방법으로, 다음과 같은 방법들이 있습니다:
#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 매크로를 써서 가변 인자를
처리할 수 있습니다.
#define DEBUG(...) fprintf(debugfd, __VA_ARGS__) DEBUG("age = %d, name = %s\n", age, namestr);가변 인자를 받는 함수와 달리, 고정 인자가 하나 이상 필요하다는 규칙은 적용되지 않습니다.
__FILE__
과 __LINE__
매크로를, 디버그용으로,
메시지 출력하는 매크로에 쓰고 싶습니다. 어떻게 하면 될까요?
__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);
#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);
Seong-Kook Shin