본문 바로가기
카테고리 없음

C언어 - 전처리기와 매크로

by kik328288 2026. 5. 5.

매크로 (define, ifdef, 실기)

C 프로그램이 실행 파일로 만들어지기까지는 일반적으로 알려진 컴파일 외에도 그보다 앞서 진행되는 단계가 있다. 바로 전처리(Preprocessing)이다. 우리가 익숙하게 사용하는 #include 한 줄 역시 사실은 컴파일러가 아닌 전처리기가 처리하는 지시문이다. 전처리기는 소스 코드에 적힌 #으로 시작하는 모든 지시어를 해석하고, 그 결과로 만들어진 새로운 소스를 비로소 컴파일러에 넘긴다(출처: cppreference — Preprocessor). 정보처리기사 실기에서도 #define으로 정의된 매크로의 치환 결과와 괄호 누락에 따른 부작용을 묻는 문제가 자주 출제된다. 본 글은 전처리 단계의 의미와 핵심 지시문, 그리고 시험 빈출 패턴을 코드 예시와 함께 정리한다. 제가 학교 C 프로젝트에서 가장 자주 만난 사고가 #define SQUARE(x) x*x 같은 괄호 누락이었는데, 한 번 SQUARE(2+3)이 25가 아니라 11이 나오는 모습을 본 후로는 매크로 정의에 모든 괄호를 다는 습관이 자리 잡았다.

전처리 단계와 #include, #define

C 컴파일 과정은 크게 네 단계로 나뉜다. 전처리기(Preprocessor) → 컴파일러(Compiler) → 어셈블러(Assembler) → 링커(Linker) 순이며, 가장 먼저 동작하는 전처리기는 소스 코드를 단순한 텍스트로 본다. 즉 전처리기는 C언어의 문법을 이해하지 않으며, 단지 #으로 시작하는 지시문을 만나면 그 지시에 따라 코드를 치환하거나 끼워 넣는 일만 한다. 여기서 링커(Linker)란 여러 개의 오브젝트 파일과 라이브러리를 하나의 실행 파일로 결합해 주는 도구로, 함수 호출의 실제 주소를 채워 넣는 마지막 단계를 책임진다.

가장 먼저 익히는 지시문은 #include이다. 이는 지정된 파일의 내용을 그 위치에 그대로 복사해 끼워 넣으라는 명령이다. 표기 방식에 따라 검색 위치가 달라진다.

#include <stdio.h>      // 시스템 표준 헤더 디렉터리에서 검색
#include "myheader.h"   // 현재 디렉터리부터 검색

같은 헤더가 여러 번 포함되는 다중 포함 문제를 막기 위해 모든 헤더 파일은 다음과 같은 헤더 가드를 두는 것이 표준이다.

// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H

void greet(void);

#endif

헤더 가드의 핵심 도구가 바로 두 번째 지시문 #define이다. #define은 식별자에 어떤 텍스트를 연결해두는 매크로 정의이며, 이후 코드에서 그 식별자를 만나면 전처리기가 정의된 텍스트로 단순 치환한다.

#define PI 3.14159
#define MAX_SIZE 100

double area = PI * r * r;
int arr[MAX_SIZE];

전처리 단계가 끝나면 코드 안의 PI는 모두 3.14159로, MAX_SIZE는 100으로 치환된다. 따라서 변수와 달리 메모리를 차지하지 않고 컴파일 결과 코드에 직접 박힌다는 특성이 있다. 이러한 단순 치환 방식은 매우 빠르고 가볍지만, 자료형 검사가 없어 잘못 사용하면 디버깅이 어려운 오류로 이어질 수 있어, 최근에는 가능하면 const 변수나 enum을 우선 사용하는 흐름이 자리잡았다.

함수 매크로와 조건부 컴파일

#define은 단순 상수뿐만 아니라 인자를 받는 함수 매크로도 정의할 수 있다. 함수 매크로는 호출 부분이 그대로 코드에 펼쳐지므로 함수 호출의 오버헤드가 없고, 자료형에 구애받지 않아 다양한 자료형에 동일한 로직을 적용할 수 있다는 장점이 있다.

#define MAX(a, b) ((a) > (b) ? (a) : (b))

int x = MAX(3, 7);          // ((3) > (7) ? (3) : (7)) → 7
double y = MAX(1.5, 2.7);   // 자료형 무관

여기서 가장 중요한 원칙이 모든 인자와 전체 식을 괄호로 감싸는 것이다. 괄호를 빠뜨리면 매크로가 펼쳐지는 위치에서 의도와 전혀 다른 연산 순서로 해석될 수 있다. 다음의 SQUARE 매크로가 대표적인 함정이다.

#define SQUARE(x) x * x        // 잘못된 정의 (괄호 없음)

int a = SQUARE(2 + 3);
// 치환 결과: 2 + 3 * 2 + 3 = 11   (의도: 25)

#define SQUARE2(x) ((x) * (x)) // 안전한 정의
int b = SQUARE2(2 + 3);        // ((2+3) * (2+3)) = 25

이러한 매크로 부작용은 정보처리기사 실기에서 단골 출제 포인트이며, 괄호 한 쌍의 유무로 결과가 달라지는 코드가 자주 등장한다. 또한 함수 매크로는 인자에 부작용이 있는 식(예: i++)을 넘기면 인자가 여러 번 평가되어 기대하지 않은 동작을 일으키므로, 이런 경우에는 매크로 대신 인라인 함수를 사용하는 것이 안전하다. 여기서 인라인 함수란 함수 호출의 오버헤드를 줄이기 위해 호출 위치에 함수 본문을 직접 펼쳐 넣도록 컴파일러에 권장하는 키워드(inline)를 가진 함수를 가리키며, 매크로와 달리 자료형 검사가 그대로 적용된다.

조건부 컴파일은 특정 조건이 참일 때만 코드의 일부를 컴파일에 포함시키는 강력한 기능이다. #ifdef, #ifndef, #if, #else, #elif, #endif 같은 지시문이 사용되며, 디버그 빌드와 릴리즈 빌드를 분리하거나 운영체제별로 다른 코드를 작성할 때 표준적으로 활용된다.

#define DEBUG

#ifdef DEBUG
    printf("디버그 모드: x = %d\n", x);
#endif

#if defined(__linux__)
    printf("리눅스 환경\n");
#elif defined(_WIN32)
    printf("윈도우 환경\n");
#else
    printf("기타 환경\n");
#endif

DEBUG가 정의된 경우에만 디버그 출력 코드가 컴파일되므로, 릴리즈 빌드에서는 단순히 #define DEBUG 한 줄만 지우면 디버그 코드 전부가 자동으로 제거된다.

정보처리기사 실기 빈출 매크로 예제

정보처리기사 실기에서 전처리기와 매크로는 주로 단순 치환의 결과를 손으로 추적하는 형태로 출제된다(출처: Q-Net 정보처리기사).

유형 1 — 매크로 상수 치환.

#define N 5
#define M (N + 2)

int main(void) {
    int arr[M];
    for (int i = 0; i < M; i++) arr[i] = i;
    printf("%d\n", arr[6]);   // 6
    printf("%d\n", M * 2);    // 14  ← (5+2)*2
    return 0;
}

만약 매크로 정의에서 괄호를 빼면 5 + 2 * 2가 되어 9가 출력되는 함정이 있다.

유형 2 — 함수 매크로의 부작용.

#define MUL(x, y) x * y

int main(void) {
    int a = MUL(2 + 3, 4);    // 2 + 3 * 4 = 14 (의도: 20)
    int b = MUL(2, 3 + 4);    // 2 * 3 + 4 = 10 (의도: 14)
    printf("%d %d\n", a, b);  // 14 10
    return 0;
}

괄호가 없는 함수 매크로의 위험성을 정확히 묻는 패턴이다. 솔직히 제 경험상 학교 시험에서 정확히 이 패턴을 잘못 해석해 두 문제를 동시에 틀린 적이 있고, 그 한 번의 실수가 "매크로는 텍스트 치환이지 함수 호출이 아니다"라는 한 줄을 손끝으로 새기게 만들었다.

유형 3 — 조건부 컴파일과 #if.

#define VERSION 2

int main(void) {
#if VERSION == 1
    printf("v1\n");
#elif VERSION == 2
    printf("v2\n");
#else
    printf("etc\n");
#endif
    return 0;
}
// 출력: v2

VERSION 매크로의 값에 따라 어느 분기가 컴파일에 포함되는지 추적하는 기본 패턴이다. 조건이 거짓인 분기는 컴파일러에 전달되지 않으므로 출력에도 나타나지 않는다.


메타 디스크립션: C언어의 전처리 단계와 #include·#define·#ifdef 지시문의 동작 방식, 함수 매크로의 괄호 사용 원칙과 부작용, 조건부 컴파일의 활용, 그리고 정보처리기사 실기 빈출 3패턴을 코드 예시와 함께 정리합니다.


소개 및 문의 · 개인정보처리방침 · 면책조항

© 2026 블로그 이름