ANSI C 버전
================================================================================
#include <stdio.h> #include <stdarg.h>
int average(int first, ...);
void main( void ) { /* Call with 3 integers (-1 is used as terminator). */ printf("Average is: %dn", average(2, 3, 4, -1));
/* Call with 4 integers. */ printf("Average is: %dn", average(5, 7, 9, 11, -1));
/* Call with just -1 terminator. */ printf("Average is: %dn", average(-1));
getchar(); }
/* Returns the average of a variable list of integers. */ int average(int first, ...) { int count = 0, sum = 0, i = first; va_list marker;
va_start(marker, first); /* Initialize variable arguments. */
while (i != -1) { sum += i; count++; i = va_arg(marker, int); } va_end(marker); /* Reset variable arguments. */
return (sum ? (sum / count) : 0); }
================================================================================
#include <stdio.h> #include <string.h> #include <stdarg.h>
void myprintf(char *sFirst, ...) { va_list args; char *str;
va_start(args, sFirst); printf("sFirst : %sn", sFirst);
while(1) { str = va_arg(args, char *);
if (strcmp(str, "") == 0) break; else { printf("va_list: %sn", str); }
}
str = va_arg(args, char *); va_end(args); }
void main() { myprintf("sbs", "mbc", "kbs", ""); getchar(); }
================================================================================
#include <stdarg.h> #include <stdio.h> #define ERR_FUNC(fmt, args...) err_func(__FILE__, __LINE__, fmt, ## args) void err_func(const char *, const int, char *, ...); void err_func(const char *name, const int line, char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "Error in %s, line %i:", name, line); vfprintf(stderr, fmt, ap); va_end(ap); } int main() { ERR_FUNC("value=%d",12); }
================================================================================
GCC버전
================================================================================
#include <stdarg.h> #include <stdio.h> #define ERR_FUNC(fmt, args...) err_func(__FILE__, __FUNCTION__, __LINE__, fmt, ## args) void err_func(const char *, const char *, const int, char *, ...) __attribute__ ((format (printf, 4, 5))); void err_func(const char *name, const char *func, const int line, char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "Error in %s, line %i, in %s():", name, line, func); vfprintf(stderr, fmt, ap); va_end(ap); } int main() { ERR_FUNC("value=%d",12); }
================================================================================ Token-Pasting Operator (##) The double-number-sign or “token-pasting” operator (##), which is sometimes called the “merging” operator, is used in both object-like and function-like macros. It permits separate tokens to be joined into a single token and therefore cannot be the first or last token in the macro definition.
If a formal parameter in a macro definition is preceded or followed by the token-pasting operator, the formal parameter is immediately replaced by the unexpanded actual argument. Macro expansion is not performed on the argument prior to replacement.
Then, each occurrence of the token-pasting operator in token-string is removed, and the tokens preceding and following it are concatenated. The resulting token must be a valid token. If it is, the token is scanned for possible replacement if it represents a macro name. The identifier represents the name by which the concatenated tokens will be known in the program before replacement. Each token represents a token defined elsewhere, either within the program or on the compiler command line. White space preceding or following the operator is optional.
This example illustrates use of both the stringizing and token-pasting operators in specifying program output:
#define paster(n) printf("token" #n " = %d", token##n)int token9 = 9; If a macro is called with a numeric argument like paster(9);
the macro yields printf("token" "9" " = %d", token9);
which becomes printf("token9 = %d", token9);
-------------------------------------------------------------------------------------------------------------------------
printf() 함수처럼 매크로도 가변 개수의 인수를 취하도록 만들 수 있는데, 이를 가변 매크로(variadic macro)라고 합니다. C99 표준 이전에 가변 매크로를 만드는 방법은 다음과 같았습니다:
#ifdef DEBUG
#define TRACE(a) printf a
#else
#define TRACE(a)
#endif
이렇게 한 뒤 실제 사용은
TRACE(("count = %dn", count));
처럼 합니다. 괄호를 두 개 쓰는 것은 일종의 꼼수인데, 매크로 전개를 하면 ("count = %dn", count) 부분이 a로 치환이 되면서 결과적으로는
printf("count = %dn", count);
이 됩니다. 원하는 결과를 얻긴 했지만 보기가 별로 좋지 않지요? 하지만 프리프로세서 차원에서 가변 매크로를 지원하지 않는 컴파일러의 경우 이 방법이 유일합니다.
gcc의 경우에는 ...을 이용해서 깔끔하게 만들 수 있습니다:
#define TRACE(args...) fprintf (args)
이 방법의 장점은 괄호를 한 개만 써도 된다는 점인데, 단점은 비표준이라 다른 컴파일러에서 동작되리란 보장이 없다는 점입니다. 그래서 C99에서는 가변 매크로 정의 방법을 표준에 도입하여 __VA_ARGS__란 매크로를 추가로 사용합니다:
#define TRACE(arg, ...) printf(arg, __VA_ARGS__)
위의 gcc 전용 방법과 달리 arg와 ... 사이에 쉼표가 들어간 것을 볼 수 있습니다. __VA_ARGS__는 ... 부분을 받고요. 언뜻 보면 깔끔하게 느껴지지만 실은 이 C99 표준은 심각한 결함이 있습니다. 예를 들어
TRACE("here!n");
처럼 인수를 하나만 사용하는 경우 실제 확장은
printf("here!n", );
처럼 되어 버립니다. 당연히 컴파일러 에러가 나겠지요. 그래서 이에 대한 해결책으로
#define TRACE(arg, ...) printf(arg, ##__VA_ARGS__)
처럼 __VA_ARGS__앞에 ##를 붙이면 불필요한 쉼표가 자동으로 삭제됩니다.
[추가] pyrasis님의 지적에 따르면
#define TRACE(...) printf(__VA_ARGS__)
도 사용이 가능합니다.
C99 가변 매크로의 또 한가지 단점은 C99 표준을 따르지 않는 컴파일러에서 사용이 불가능하다는 점입니다. 비주얼 C가 대표적인 예인데, 최신 VS2005에서도 C99 지원은 논외로 밀려나 있습니다. 그래서 맨 위의 괄호 두 개 쓰는 방법처럼 가장 지저분한 방법을 쓰는 것이 일반적입니다.
여기서는 __noop()란 함수를 이용해서 깔끔하게 구현하는 방법을 소개하려고 합니다. __noop() 함수는 일반 함수가 아니고 내재 함수(intrinisic function)라고 해서 컴파일러에 의해 생성되고 처리되는 함수입니다. 이 함수가 하는 일은 아무것도 하지 않는 것이 되겠습니다(써놓고 보니 어감이 약간 이상...^^). 인수 개수와 타입은 물론 상관이 없습니다. 전부 무시됩니다. 실제 사용은
#ifdef DEBUG
#define TRACE printf
#else
#define TRACE __noop
#endif
처럼 합니다. DEBUG가 정의되지 않았을 때
TRACE("count = %dn", count);
문장은
__noop("count = %dn", count);
로 전개되고, 결과는 기대한 대로 아무일도 일어나지 않습니다.
실제로 Wine 소스를 VC에서 컴파일되도록 고칠 때 __noop를 썼더니 깔끔하게 되더군요. |