C언어를 다년간 사용한 프로그래머들도 종종 매크로 연산자에 대해서 정확하게 이해하고 있지 못한 경우가 많다. 매크로를 사용하지 않는다고 대답할 수도 있지만, 이미 복잡한 매크로를 사용한 수많은 소스가 있다는 점을 생각해 본다면 분명하게 이해해 두는 것이 정신 건강에 좋을 것이다. 복잡한 매크로 표현 식을 구성하는데 많이 사용되는 방법에 대해서 살펴보자.
첫째, 문자열 리터럴은 합쳐진다. 이는 매크로라기 보다는 C언어의 특징이다. 이 기능을 사용하면 긴 출력 문장을 손쉽게 여러 개의 부분 문자열로 나눌 수 있다. 또한 다음에 소개될 매크로 연산자를 사용할 때의 표현식도 좀 더 풍부하게 구성할 수 있다는 장점이 있다.
printf("이름: %s\n"
"나이: %d\n"
"전화번호: %s\n", a, b, c);
위의 코드를 보자. printf 다음에는 총 세 개의 연속된 문자열 리터럴이 나타난다. 이 세 개의 리터럴은 합쳐져서 "이름: %s\n나이: %d\n전화번호: %s\n" 과 동일한 문자열이 된다.
둘째, ## 연산자를 사용해서 토큰을 합성해서 만들어 낼 수 있다. ##은 합치기 연산자 이다. 다음과 같은 기능을 생각해 보자. COUNT(start)라고 선언을 하면 DWORD startCnt; 라는 변수를 선언하는 기능이다. 이를 매크로를 이용해서 만들어 보면 아래와 같다.
#define COUNT(val) DWORD val##Cnt
셋째, # 연산자는 전달된 인자를 문자열로 변환시킨다. start를 매크로 인자로 전달했다면 "start"가 된다는 말이다. 변수 값을 출력하는 매크로를 생각해 보자. PRINT(start)를 하면 화면에 start = 3과 같은 형태로 출력하고 싶은 경우다. 이럴 땐 아래와 같이 매크로를 만들면 된다. #연산자와 위에서 소개한 문자열 리터럴이 합쳐진다는 점을 이용한 것이다. 좀 꽁수 같이 보인다면 두 번째 방식같이 구성할 수 도 있다.
#define PRINT(val) printf(#val " = %d", val)
#define PRINT(val) printf("%s = %d", #val, val)
넷째, #@ 연산자는 전달된 인자를 문자로 변환시킨다. a를 매크로 인자로 전달했다면 'a'를 만들어 주는 것이다. 아래와 같이 전달된 인자에 대한 문자를 생성해주는 매크로를 예로 들 수 있다.
#define makechar(val) #@val
다섯째, 매크로의 내용이 복잡하고 한 줄 이상의 표현식이 필요한 경우엔 주로 do ~ while(0)문을 사용한다. 이렇게 하는 이유는 do ~ while(0)가 하나의 구문으로 해석되고 자체 블록을 가지기 때문이다. 이것을 단순 괄호({, })로 대체하면 안 된다. 중첩 if문에서 에러가 나기 때문이다. 주로 아래와 같이 사용한다.
#define COMPLEX\_MACRO(a,b,c,d) do { \
int complex\_variable; \
// some processing; \
} while(0)
여섯째, 릴리즈 버전에서는 무시되는 매크로를 구성하는 경우다. 주로 디버깅 출력을 하는 매크로가 여기에 속한다. Visual C++ 6.0에서는 '?' 연산자를 사용해서 쇼트 서킷을 구성하는 방법을 주로 사용했다. 하지만 이 후 출시된 Visual C++에는 __noop이라는 내장 함수를 가지고 있다. 이 함수는 인자를 모두 무시하는 기능을 한다. 따라서 예전에 복잡한 쇼트서킷을 사용했던 함수를 두 번째와 같이 간단하게 구성할 수 있다.
#define TRACE 1 ? 0 : OutputDebugString
#define TRACE \_\_noop