
C언어 헤더 가드는 파일을 나누기 시작하면 꼭 만나게 되는 기본 장치입니다. 처음에는 #ifndef, #define, #endif가 주문처럼 보이지만, 실제 목적은 단순합니다. 같은 헤더 내용이 한 번의 컴파일 단위 안에서 여러 번 들어오지 못하게 막는 것입니다.
이 글에서는 include를 여러 번 하면 왜 문제가 생기는지, 헤더 가드가 어떻게 막는지, #pragma once와는 무엇이 다른지 차근차근 정리하겠습니다.

C언어 헤더 가드가 필요한 이유: include는 파일을 복사해 넣는 것에 가깝다
#include는 헤더 파일의 내용을 현재 위치에 가져오는 전처리 지시문입니다. 그래서 A.h와 B.h가 둘 다 같은 Common.h를 include하면, 결과적으로 Common.h 내용이 한 컴파일 단위 안에 두 번 들어올 수 있습니다.
// common.h
typedef struct User {
int id;
} User;
// a.h
#include "common.h"
// b.h
#include "common.h"
// main.c
#include "a.h"
#include "b.h"이렇게 되면 User 구조체 정의가 두 번 보일 수 있습니다. 선언은 종류에 따라 여러 번 가능할 때도 있지만, 타입 정의나 함수 정의처럼 중복되면 문제가 되는 내용도 많습니다.
헤더 가드는 어떻게 막을까
헤더 가드는 매크로가 아직 정의되지 않았을 때만 헤더 내용을 통과시키는 방식입니다. 한 번 통과하면 매크로가 정의되므로, 다음 include부터는 본문이 건너뛰어집니다.
// common.h
#ifndef COMMON_H
#define COMMON_H
typedef struct User {
int id;
} User;
#endif처음 common.h가 포함될 때는 COMMON_H가 없으므로 본문이 들어갑니다. 그 안에서 COMMON_H가 정의됩니다. 두 번째로 common.h가 포함될 때는 이미 COMMON_H가 있으므로 #ifndef 조건이 거짓이 되어 본문이 들어가지 않습니다.
헤더 가드의 핵심은 헤더 파일을 한 번만 읽는 것이 아니라, 같은 컴파일 단위 안에서 같은 내용이 한 번만 보이게 하는 것입니다.
이름은 어떻게 정하면 좋을까
가드 매크로 이름은 프로젝트 안에서 충돌하지 않게 정해야 합니다. 보통 파일 경로와 파일명을 대문자, 밑줄 형태로 조합합니다.
#ifndef MYAPP_MODEL_USER_H
#define MYAPP_MODEL_USER_H
typedef struct User {
int id;
} User;
#endifCOMMON_H처럼 너무 일반적인 이름은 작은 예제에서는 괜찮지만, 큰 프로젝트에서는 충돌 위험이 있습니다. 헤더 경로를 반영한 이름이 더 안전합니다.
#pragma once는 무엇이 다를까
#pragma once는 이 파일을 한 번만 포함하라고 컴파일러에 알려주는 방식입니다. 짧고 실수할 여지가 적어서 많이 쓰입니다.
#pragma once
typedef struct User {
int id;
} User;다만 #pragma once는 널리 지원되지만 C 표준 자체의 전처리 문법은 아닙니다. 반면 #ifndef 방식의 헤더 가드는 전처리 조건부 컴파일을 이용하는 더 전통적이고 이식성 높은 방식입니다. GCC 문서의 Once-Only Headers와 Microsoft의 once pragma 설명을 함께 보면 차이가 더 분명합니다.
헤더 가드가 해결하지 못하는 것
- 헤더 안에 함수 정의를 함부로 넣어서 생기는 링크 에러
- 전역 변수 정의를 헤더에 넣어서 생기는 중복 정의 문제
- 순환 include로 설계가 꼬이는 문제
헤더 가드는 중복 include를 막는 장치이지, 헤더 설계를 자동으로 좋게 만들어 주는 도구는 아닙니다. 헤더에는 선언을 두고, 실제 정의는 .c 파일에 두는 기본 원칙도 함께 지켜야 합니다.
실무 체크리스트
- 모든 .h 파일에는 헤더 가드나 #pragma once를 둔다
- 가드 매크로 이름은 프로젝트 경로를 반영해 충돌을 줄인다
- 헤더에는 타입 선언, 함수 선언, 매크로처럼 필요한 최소 정보만 둔다
- 전역 변수 정의와 일반 함수 정의는 되도록 .c 파일에 둔다
마무리
C언어 헤더 가드는 include가 여러 경로로 반복될 때 같은 내용이 중복해서 보이는 문제를 막습니다. #ifndef, #define, #endif 패턴은 처음엔 낯설지만, 결국 “이 헤더 본문은 한 번만 통과시키자”는 뜻입니다.
파일을 나누기 시작했다면 헤더 가드는 선택이 아니라 기본 습관에 가깝습니다. 이 습관이 잡히면 include 구조와 C 파일 분리도 훨씬 안정적으로 다룰 수 있습니다.
여러 파일로 나눴을 때 선언과 정의가 어떻게 갈리는지까지 이어서 보고 싶다면 C언어 extern 글을 함께 읽으면 좋습니다. 포인터와 배열 선언이 헷갈린다면 C 언어 포인터와 배열 차이도 이어서 참고하기 좋습니다.