Quick Reference
헤더는 여러 translation unit가 공유하는 선언·타입 계약이고, .c 파일은 한 곳에서 소유하는 정의입니다. include guard는 같은 헤더의 중복 삽입을 막습니다.
/* state.h */
#ifndef STATE_H
#define STATE_H
extern int g_count;
void reset_count(void);
#endif/* state.c */
#include "state.h"
int g_count = 0; /* 정의는 한 .c 파일에 둔다. */
void reset_count(void) {
g_count = 0;
}header에는 선언·타입·extern 선언을, .c 파일 하나에는 일반 함수와 전역의 정의를 둡니다. 각 파일 안에서만 쓸 작은 도우미는 static inline으로 header에 둘 수 있습니다.
문법
헤더는 다른 .c 파일이 알아야 하는 선언과 타입 계약을 공유하는 곳입니다. 반대로 실제 함수 본문과 전역 변수 정의는 .c에 두는 편이 기본입니다.
C는 파일별로 따로 컴파일합니다. 다른 파일의 함수나 타입을 쓰려면 "미리" 그 모양을 알아야 합니다.
/* math_utils.h */
typedef struct {
double x;
double y;
} Point;
double distance(Point a, Point b);이 헤더를 main.c, renderer.c가 같이 include하면 둘 다 같은 인터페이스를 공유합니다.
구성 요소
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
typedef struct {
double x;
double y;
} Point;
double distance(Point a, Point b);
#endif헤더가 헤더를 포함하는 구조에서는 같은 내용이 여러 번 들어오기 쉽습니다. include guard가 없으면 타입 재정의나 선언 충돌이 바로 납니다.
#pragma once도 비슷한 역할이지만, 가장 기본적인 설명은 include guard 기준으로 익혀 두는 편이 안전합니다.
#pragma once는 널리 지원되지만 표준 C 지시문은 아닙니다. 헤더가 자신이 사용하는 size_t, FILE 같은 선언에 필요한 표준 헤더를 직접 include하는 self-contained 형태여야 include 순서에 의존하지 않습니다.
전역 변수도 같은 원리입니다. 헤더에는 extern 선언만 두고, 실제 정의는 .c 파일 하나에만 둡니다.
/* state.h */
extern int g_count;
/* state.c */
int g_count = 0;자주 실수하는 패턴
헤더에 정의를 넣는 경우입니다.
/* bad.h */
int counter = 0; // 정의
int add(int a, int b) { // 정의
return a + b;
}이 헤더를 두 .c 파일이 include하면 링크 단계에서 multiple definition이 납니다.
일반 external 함수 정의는 .c 한 곳에, 공유 전역은 헤더의 extern 선언과 .c 한 곳의 정의로 둡니다. typedef, enum, macro는 헤더에 둘 수 있고, 헤더의 작은 구현은 static inline일 때만 각 translation unit에 독립 정의가 생긴다는 점을 의식합니다.
빠른 점검
- 헤더는 선언
.c는 정의- 전역 변수 공유는 헤더에
extern - 중복 포함 방지는 include guard
- 작은 도우미만
static inline
주의할 점
헤더에 함수 정의나 전역 변수 정의를 넣으면 "컴파일은 되는데 링크에서 터지는" 경우가 많습니다. C에서 헤더 문제는 보통 전처리 단계가 아니라 링크 단계에서 더 크게 드러난다는 점을 기억해 두는 편이 좋습니다.
참고 링크
2 sources