매크로(Macro)는 **‘큰 단위, 한 번에 묶은 것’**을 뜻하는 그리스어 makros 에서 나온 말입니다.
우리가 흔히 쓰는 micro(아주 작은 단위)의 반대 개념이기도 합니다.
즉, 매크로란 여러 작은 작업을 하나의 큰 단위로 묶는 개념입니다.
1. 일상에서의 매크로 사용
예를 들어 프로그램을 종료할 때 매번 다음과 같은 반복 작업을 한다고 가정해 보겠습니다.
1. 메뉴를 선택
2. 저장을 선택
3. 프로그램 종료 버튼을 선택
이처럼 반복적이고 단순한 작업이 있을 때, 이를 하나로 묶어 클릭 한 번에 처리하는 방식이 바로 매크로입니다.
게임에서도 같은 개념을 쉽게 찾아볼 수 있습니다.
Q → W → E → R
이러한 일련의 키 입력을 F5 키 하나로 등록해 실행할 수 있다면 훨씬 편리할 것입니다.
정리하면, 매크로란 자주 반복되는 작은 작업들을 하나의 큰 규칙으로 묶어두는 방식을 말합니다.
2. C/C++에서의 매크로 사용
프로그래밍에서 가장 단순한 매크로는 다음과 같습니다.
#define PI 3.14
#define은 컴파일 전에, 지정된 이름을 뒤에 오는 텍스트로 그대로 치환합니다.
따라서 PI라는 이름을 사용하면, 실제로는 3.14라는 텍스트로 바뀌게 됩니다.
또는 다음과 같이 자주 쓰는 코드를 하나로 묶을 수도 있습니다.
#define LOG(x) UE_LOG(LogTemp, Log, TEXT(x))
이처럼 반복되는 코드를 하나의 이름으로 정리하는 데 사용할 수 있습니다.
3. C/C++ 매크로의 확장
여기까지 보면 매크로는 다소 단순해 보입니다.
“굳이 이걸 써야 하나?”라는 생각이 들 수도 있습니다.
하지만 매크로는 텍스트 하나가 아니라, 코드 묶음을 만들어낼 때 더 큰 의미가 있습니다.
아래처럼 거의 동일한 함수들이 반복된다고 가정해 보겠습니다.
int Add(int a, int b)
{
return a + b;
}
int Sub(int a, int b)
{
return a - b;
}
이 패턴을 매크로로 묶으면 다음과 같이 표현할 수 있습니다.
\ : 한 줄이 길어질 때 여러 줄로 나누기 위해 사용하는 매크로 기호
#define MAKE_FUNC(name, op) \
int name(int a, int b) \
{ \
return a op b; \
}
MAKE_FUNC(Add, +)
MAKE_FUNC(Sub, -)
전처리 이후, 컴파일러가 실제로 보게 되는 코드는 다음과 같습니다.
int Add(int a, int b) { return a + b; }
int Sub(int a, int b) { return a - b; }
# : 매크로 인자를 문자열로 만들기
# 는 매크로 인자를 문자열로 치환하라는 의미입니다.
즉, #T 는 "T" 로 치환됩니다.
#define REGISTER_COMPONENT(T) \
RegisterComponent(#T);
REGISTER_COMPONENT(Player)
전처리 결과는 다음과 같습니다.
RegisterComponent("Player");
## : 두 토큰을 붙이기
##는 두 텍스트를 하나로 붙여주는 역할을 합니다.
#define MAKE_GETTER(type, name) \
type Get##name() const { return name; }
사용 예시는 다음과 같습니다.
class Player
{
int HP;
int MP;
public:
MAKE_GETTER(int, HP)
MAKE_GETTER(int, MP)
};
전처리 후 실제 코드는 다음과 같습니다.
class Player
{
int HP;
int MP;
public:
int GetHP() const { return HP; }
int GetMP() const { return MP; }
};
4. 언리얼에서 사용 예시
언리얼에서는 UE_LOG() 라는 함수가 존재합니다.
그리고 코드를 추적하면, 아래와 같이 매크로로 되어있습니다.
#define DECLARE_LOG_CATEGORY_EXTERN(CategoryName, DefaultVerbosity, CompileTimeVerbosity) \
extern struct FLogCategory##CategoryName : public FLogCategory<ELogVerbosity::DefaultVerbosity, ELogVerbosity::CompileTimeVerbosity> \
{ \
UE_FORCEINLINE_HINT FLogCategory##CategoryName() : FLogCategory(TEXT(#CategoryName)) {} \
} CategoryName;
그리고 컴파일을 하면 다음과 같이 구조체 타입의 전역 변수를 생성해 냅니다.
extern struct FLogCategoryLogTemp
: public FLogCategory<ELogVerbosity::Log, ELogVerbosity::All>
{
UE_FORCEINLINE_HINT FLogCategoryLogTemp()
: FLogCategory(TEXT("LogTemp")) {}
} LogTemp;
위 코드는 우리가 눈으로 볼 수 없는 코드이기 때문에 매크로만 보고는 분석이 어렵고, 그저 이걸 작성한 개발자가 대단하다고 생각될 뿐입니다.
6. 정리
매크로는 코드의 의미를 이해하지 않습니다.
그저 텍스트를 규칙에 따라 조립해 끼워 넣을 뿐입니다.
그래서 매크로 코드는 코드처럼 보이지 않고, 의도가 한눈에 들어오지 않는 경우가 많습니다.
이러한 이유로 매크로는 가독성을 어느 정도 포기하는 대신 코드의 생산성을 극대화하기 위해 주로 코어 개발자들에 의해 만들어집니다. 따라서 이미 정의된 매크로를 가져다 쓰는 경우가 일반적입니다.
'C++' 카테고리의 다른 글
| 스택(Stack)과 힙(Heap) : 책임과 수명의 차이 (0) | 2026.01.08 |
|---|---|
| 복사의 종류와 차이점 (0) | 2026.01.08 |
| 전방 선언 (0) | 2026.01.05 |
| TCHAR 에 대한 이해 (0) | 2026.01.04 |
| const 는 어디에 위치하느냐에 따라 의미가 달라집니다 (0) | 2026.01.02 |