본문 바로가기

3.구현/C or C++

고급 매크로 기법 Variadic macro

매크로는 작업을 편하게 해주지만, 디버깅하기 매우 힘들게 만드는 양날의 칼과 같습니다. 그래서 가급적 매크로를 사용하지 않고 해결할 수 있으면 그렇게 하는게 좋습니다.

C++에서는 모든 작업을 객체로 캐슐화하기에 매크로 사용하지 않아도 작업을 쉽게 사용할 수 있는 경우가 많습니다. 그러나 c인 경우는 그렇지 못한 경우가 많으며 빈번히 매크로를 사용하는 경우가 많습니다.

솔찍히 같은 코드를 반복하는 경우(안좋은 설계)에 매크로로 만들어서 변경되는 부분만 입력해주면 쉽게 작업이 가능합니다. 그래도 가급적 다른 방법을 활용하는게 좋다고 생각합니다.

작성: http://ospace.tistory.com/(ospace114@empal.com) 2012.06.16

Variadic macro 간략한 내용

매크로 중에서 가변인자 매크로가 있습니다. 함수에서는 많이 본적이 있지만, 매크로인 경우는 본 적이 없는 분이 더 많을 것으로 생각합니다. 그만큼 많이 사용하지도 않지만 사용하기 어렵기도 합니다.

먼저 기본적인 형식을 보겠습니다.

#define VARG_MACRO(...)  func(__VA_ARGS__)

가변인자 함수를 사용하신 분은 쉽게 알 수 있을 거라 생각합니다. 일단 1개 이상의 여러 인자를 받을 부분을 "..."형태로 표시했습니다. 물론 별도 인자를 둘 수도 있습니다.

#define VARG_MACRO(p, ...)  func(p, __VA_ARGS__)

주 의 할 것은 "..."은 맨 뒤에 와야합니다. 위에 func() 함수를 호출하는게 그때 "..."에 해당하는 가변 인자들과 대응되는 __VA_ARGS__를 사용합니다. 예를 들어 다음과 같이 매크로를 사용했을 경우에 매크로를 확장해봅니다.

VARG_MACRO(param, a, b, c);

>>매크로 확장

func(param, a, b, c);

매크로 뒤쪽의 인자 "a, b, c"가 __VA_ARGS__ 대신에 값이 치환된다.

Variadic macro 사용 예

이른 가변인자 매크로의 간단한 사용예로 로그 출력 기능이 있습니다.
printf() 함수는 값 출력할 때에 매우 유용한 함수 입니다. 그런데 printf() 함수에 인자는 가변적입니다. 예를 들어 현재 함수 명과 현재 줄 수를 출력하는 로그가 있다고 합시다. 그러면 다음 처럼 작성할 수 있습니다.

fprintf("(%s:%d) %s\n", __func__, __LINE__, "로그 메시지");

근데 문제점은 __func__와 __LINE__은 현재 함수를 벗어나면 다른 값이 들어 갑니다. 예를 들어 이를 출력하는 함수가 따로 만든다고 하면 다음처럼 될 것 입니다.

void log(const char* msg) {
  fprintf("(%s:%d) %s\n", __func__, __LINE__, msg);
}

//usage
int main() {
  log("로그 메시지");
  return 0;

}

아마 위처럼 생각할 수 있습니다.
이렇게 되면 해당 함수명과 라인수는 엉뚱한 곳의 정보가 표시됩니다. 위의 예에서는 main함수의 6라인이라고 출력되야하는데 실제로는 log함수의 2라인이라고 출력될 것입니다.
즉 로그 출력 위치에서 로그가 바로 출력되야 합니다.

#define LOG(m) fprintf("(%s:%d) %s\n", __func__, __LINE__, (m))
//usage
int main() {
  LOG("로그 메시지");
  return 0;
}

LOG를 대문자로 사용한 이유는 전통적으로 매크로는 대문자로 사용하기 때문입니다. 이번에는 제대로 main함수의 라인 숫자가 출력된다.
근데 당연히 로그 출력할 때 문자열만 출력 되는 것이 아니라, 여러 변수 값들을 출력하고 싶을것이다.
예를 들어 다음과 같이 printf같은 로그 출력을 하고 싶다면 어떻게 할까?

LOG("key=%s, value=%d\n", key, value);

매크로에 들어 있는 개수가 가변적으로 변한다는 말이다. 이때 필요한 것이 가변 인자 매크로이다. 위의 내용을 다음과 같이 변경하면 쉽게 가변 인자를 가진 로그 출력 기능을 가질 수 있다.

#define LOG(...) printf(__VA_VARGS__)

단순하다. LOG매크로에 넘어오는 인자들이 __VA_VARGS__에 치환되어 입력된다. 이를 앞의 함수 명과 라인수를 출력하는 로그 매크로로 변경하면 다음과 같다.

#define LOG(...) do { printf("(%s:%d)", __func__, __LINE__); printf(__VA_ARGS__); }while(0)

앞 뒤의 do~while은 매크로를 보호하기 위한 장치이다. 여러 줄의 매크로를 작성할 때 유용한 팁이다.
여기서 더 보완해야하지만, 이 정도만 되도 간단한 유연한 로그 출력이 가능하다.

고급 기법: Variable arguement macro

이 번에는 로그의 인자 개수에 따라서 다른 매크로를 불러오기 위한 부분이다. 앞의 printf는 자체적으로 가변인자를 받을 수 있고, 함수 하나만 호출 하기에 쉽게 가변인자 호출이 가능하지만, 인자 개수에 따라 다른 매크로나 다른 함수를 호출할 경우는 불가능하다.
이번은 가변 인자 매크로의 인자 개수를 얻어서 인자 개수를 이용하여 호출하는 매크로를 정하는 기법이다.
인자 개수를 5개로 제한하겠다. 이는 아래 예를 보면 알겠지만 처리할 수 최대 인자 개수에 맞는 매크로를 미리 만들어야 하기 때문이다.
먼저 매크로의 인자 개수를 얻는 매크로이다.

#define _NARGS(_1,_2,_3,_4,_5,N,...) N
#define NARGS(...) _NARGS(__VA_ARGS__, 5, 4, 3, 2, 1)

NARGS매크로에 인자를 넣으면 해당 인자 값이 나온다. 이 부분만 알면 모든 것이 끝난다.

printf("%d", NARGS(a,b));

먼저 NARGS 매크로를 풀어보자.

_NARGS(a, b, 5, 4, 3, 2, 1)

다시 _NARGS매크로가 생성되고 다시 이 매크로를 풀면 된다. 그러면, _NARGS 매크로에 앞의 5개 인수와 일치되고, 그 이상되는 인자는 가변 인자로 매치된다. 그렇게 보면 N에 해당하는 값이 나오게 된다.

a, b, 5, 4, 3, 2, 1
_1, _2, _3, _4, _5, N, ...

1은 __VA_ARGS__로 매칭되므로 아무리 긴 인자(?)가 와도 모두 가변 인자로 매치된다. 위의 결과에 보듯이 N에는 2가 매치되고 _NARGS 매크로의 치환되는 값은 N이기에 N대신에 값 2를 얻을 수 있다.
한가지 약점은 인자가 최소 1개 이상이어야 한다. 0개는 있을 수 없다. 이 말은 _NARGS 매크로에 의해 __VA_ARGS__의 매크로가 비게 되지만 다음에 쉼표(,)가 있기 때문에 값이 있다고 간주해서 처리하게 된다.
이를 이용해서 실제 매크로룰 호출해보자. TRACE라는 매크로가 있다고 하자.

#define _CAT(a,b) a ## b
#define CAT(a,b) _CAT(a,b)
#define TRACE(...) CAT(TRACE, NARGS(__VA_ARGS__))(__VA_ARGS__)
#define TRACE1(p1) ...
#define TRACE2(p1, p2) ...
#define TRACE3(p1, p2, p3) ...

0개의 인자를 처리하는 부분은 다른 분이 올리셨는데 도무지 이해되지 않아서 나름대로 간단한 방법을 만들어서 사용했다. 약간의 편법을 이용했지만, 더 단순하는 장점이 있다. (안된다. ㅡ.ㅡ;)

#define _NARGS(_0,_1,_2,_3,_4,_5,N,...) N
#define NARGS(...) _NARGS(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define TRACE(...) CAT(TRACE,NARGS(0,__VA_ARGS__))(__VA_ARGS__)
#define TRACE0() ...
#define TRACE1(p1) ...
#define TRACE2(p1, p2) ...

VC 호환성 문제

다음은 VC에서 가변인자 매크로이다. 굳이 VC을 따로 두는 이유가 있다. VC도 가변 인자 매크로를 지원한다. 그러나, 표준을 지원하지 않는다. 그래서 위의 매크로를 실해하면 에러가 발생한다.

#define L_BRACK (
#define R_BRACK )
#define _NARGS(_1,_2,_3,_4,_5,N,...) N
#define NARGS(...) L_BRACK __VA_ARGS__, 5, 4, 3, 2, 1 R_BRACK

#define _CAT(a,b) a ## b
#define CAT(a,b) _CAT (a,b)
#define TRACE(...) CAT L_BRACK TRACE, NARGS(__VA_ARGS__) R_BRACK L_BRACK __VA_ARGS__ R_BRACK
#define TRACE1(p1) ...
#define TRACE2(p1, p2) ...

좀 난해하다. ㅡ.ㅡ; 기호를 문자로 바꿔을 뿐인데 좀 헤갈린다. 사용방법은 똑같다.

결론

매크로에 의한 사용은 프로그래밍을 쉽게해주지만 또한 디버깅을 어렵게 만들기도 한다.
적절하게 사용한다면 간단하고 유지보수 쉽게 만들 수 있다.

참조

[1] Christopher Jefferson, 2010.07.17 13:03, Responses to Variadic macro to count number of arguments, http://efesx.com/2010/07/17/variadic-macro-to-count-number-of-arguments/

반응형

'3.구현 > C or C++' 카테고리의 다른 글

openssl에서 nonblock socket으로 ssl 연결  (2) 2013.05.07
데이터 값을 비트 문자열로 변환  (0) 2012.08.14
[C++0x] 람다식  (0) 2012.07.31
[C++] 콘솔입력(pipe) 성능 비교  (0) 2011.12.29
[C++] 파일 읽기 성능 비교  (0) 2011.12.28