|

C 언어 헤더 파일은 왜 나눌까: .h와 .c를 분리하는 기준

C 언어 헤더 파일은 왜 나눌까: .h와 .c를 분리하는 기준
헤더 파일은 다른 파일이 알아야 하는 약속을 모아 두고, 소스 파일은 실제 구현을 담는 쪽에 가깝습니다.

C 언어 헤더 파일은 단일 파일 예제만 볼 때는 불필요해 보입니다. 하지만 함수와 구조체가 늘어나면 어떤 파일이 무엇을 사용할 수 있는지 약속을 분리해야 합니다.

핵심은 헤더는 공개 약속, .c 파일은 실제 구현에 가깝다는 점입니다. 이 글은 C의 선언과 정의, translation unit, include guard 규칙을 기준으로 .h와 .c를 나누는 기준을 정리합니다.

C 언어 헤더 파일 분리 기준 요약 카드
헤더 파일을 나누는 이유는 코드가 길어서가 아니라, 파일 사이의 약속을 분리하기 위해서입니다.

C 언어 헤더 파일을 먼저 한 줄로 정리하면

헤더 파일은 다른 소스 파일이 사용할 이름과 모양을 미리 알려주는 파일입니다. 함수 이름, 매개변수, 반환 타입, 구조체 타입처럼 여러 파일이 공유해야 하는 선언을 둡니다.

// math_utils.h
int add(int a, int b);

// math_utils.c
#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

`main.c`는 함수 본문을 몰라도 `add`의 선언만 알면 호출 코드를 컴파일할 수 있습니다. 실제 함수 본문은 나중에 링크 단계에서 연결됩니다.


선언과 정의를 나눠서 봐야 한다

선언은 ‘이런 이름이 있고 이런 타입이다’라고 알려주는 일입니다. 정의는 실제 저장 공간이나 함수 본문을 제공하는 일입니다.

int add(int a, int b);      // declaration
int add(int a, int b) {     // definition
    return a + b;
}

헤더에 함수 정의를 아무 생각 없이 넣으면 그 헤더를 포함한 여러 .c 파일마다 같은 함수 본문이 생길 수 있습니다. 그래서 일반 함수 본문은 보통 .c 파일에 둡니다.


include guard가 필요한 이유

하나의 헤더가 여러 경로로 포함되면 같은 선언이 반복될 수 있습니다. 단순 함수 선언은 괜찮은 경우가 많지만, typedef나 구조체 정의가 얽히면 중복 문제가 커집니다.

#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int add(int a, int b);

#endif

include guard는 같은 헤더 내용을 한 번만 보게 만드는 장치입니다. `#pragma once`도 많이 쓰이지만, 전통적이고 이식성을 넓게 보는 방식은 include guard입니다.


컴파일 단위를 이해하면 분리가 쉬워진다

C에서 각 .c 파일은 전처리 후 하나의 translation unit으로 컴파일됩니다. 헤더는 독립적으로 컴파일되는 파일이라기보다, include된 위치에 내용이 펼쳐지는 파일입니다.

헤더를 include한다는 것은 다른 파일을 링크한다는 뜻이 아니라, 전처리 단계에서 내용을 가져온다는 뜻입니다. 이 차이를 알면 왜 헤더에 구현을 과하게 넣으면 문제가 생기는지 이해하기 쉽습니다.


무엇을 헤더에 넣고 무엇을 숨길까

  • 외부에서 호출할 함수 선언은 헤더에 둔다
  • 외부에서 알아야 하는 struct 타입 선언은 헤더에 둔다
  • 파일 내부에서만 쓰는 helper 함수는 static으로 .c 파일에 숨긴다
  • 전역 변수 정의는 헤더에 두지 않고, 필요하면 extern 선언만 둔다
  • 구현 세부가 바뀌어도 사용 코드가 덜 흔들리게 공개 범위를 줄인다

실무 체크리스트

  1. 두 개 이상의 .c 파일에서 같은 함수를 호출하면 헤더 선언을 검토한다
  2. 헤더에는 선언과 타입 약속을 두고 함수 본문은 .c에 둔다
  3. 모든 헤더에 include guard 또는 프로젝트 표준에 맞는 #pragma once를 둔다
  4. 헤더끼리 서로 include하는 순환 구조를 만들지 않도록 전방 선언을 검토한다
  5. 공개 API와 내부 helper를 구분해 파일 경계를 작게 유지한다

정리

C 언어 헤더 파일를 이해할 때는 문법 자체보다 코드가 표현하려는 경계를 먼저 보는 편이 좋습니다. 오늘 글의 기준은 ‘읽는 사람이 실수를 줄일 수 있는가’입니다.

함께 보면 좋은 내부 글은 C 언어 배열과 포인터는 같은 걸까, C malloc free는 왜 짝으로 봐야 할까, C 언어 argc argv란 무엇일까입니다. 외부 기준은 cppreference – Declarations, cppreference – Definitions and ODR, GCC CPP Manual – Once-Only Headers를 확인했습니다.

함께보면 좋은 글