이 글에서는 C 언어의 위생적 매크로(Hygienic Macro)에 대해 알아보겠습니다. C에서 매크로의 활용법은 널리 알려져 있지만, 때로는 식별자가 우연히 캡처되는 문제(accidental capture of identifiers) 때문에 의도한 대로 동작하지 않는 경우가 발생합니다.
다음 예제 코드를 살펴보면 매크로가 제대로 작동하지 않는 것을 확인할 수 있습니다.
문제 상황 예제
#include<stdio.h>
#define INCREMENT(i) do { int a = 0; ++i; } while(0)
main(void) {
int a = 10, b = 20;
//매크로를 a와 b에 대해 각각 호출
INCREMENT(a);
INCREMENT(b);
printf("a = %d, b = %d\n", a, b);
}
전처리(preprocessing)를 거치면 위 코드는 아래와 같이 변환됩니다.
전처리 후 코드
#include<stdio.h>
#define INCREMENT(i) do { int a = 0; ++i; } while(0)
main(void) {
int a = 10, b = 20;
//매크로를 a와 b에 대해 각각 호출
do { int a = 0; ++a; } while(0);
do { int a = 0; ++b; } while(0);
printf("a = %d, b = %d\n", a, b);
}
실행 결과
a = 10, b = 21
실행 결과를 보면 변수 a의 값은 그대로 10으로 유지되고, b만 21로 증가했습니다. 그 이유는 매크로 내부에서 선언된 지역 변수 a가 외부의 변수 a를 가리는(shadowing) 현상이 일어나기 때문입니다. 매크로가 확장되면서 내부 블록에 새로운 int a가 선언되고, ++a는 외부 변수가 아니라 이 내부 변수를 증가시키게 됩니다.
해결 방법: 위생적 매크로
이러한 문제를 해결하기 위해 위생적 매크로를 사용할 수 있습니다. 위생적 매크로란 매크로 확장 과정에서 식별자가 우연히 캡처되지 않음을 보장하는 매크로를 의미합니다. 핵심 원칙은 매크로 내부에서 프로그램 본문과 충돌할 가능성이 있는 변수 이름을 절대 사용하지 않는 것입니다.
아래 예제에서는 매크로 내부 변수 이름을 t로 변경했습니다. t는 프로그램 본문 어디에서도 사용되지 않으므로 이름 충돌이 발생하지 않습니다.
수정된 예제
#include<stdio.h>
#define INCREMENT(i) do { int t = 0; ++i; } while(0)
main(void) {
int a = 10, b = 20;
//매크로를 a와 b에 대해 각각 호출
INCREMENT(a);
INCREMENT(b);
printf("a = %d, b = %d\n", a, b);
}
실행 결과
a = 11, b = 21
이제 두 변수 모두 정상적으로 1씩 증가한 것을 확인할 수 있습니다.
추가 팁: 실무에서의 안전한 매크로 작성
실무 환경에서는 매크로 내부 변수 충돌을 더욱 확실하게 방지하기 위해 다음과 같은 기법들이 활용됩니다.
- 내부 변수 이름 앞뒤에 밑줄(
_)을 붙여__t형태로 명명하여 충돌 가능성 최소화 - 토큰 붙여넣기 연산자(
##)를 활용해 고유한 변수 이름 생성 - 가능하다면 매크로 대신 인라인 함수(inline function)를 사용하여 타입 안정성까지 확보