출시·고도화 중
C 안내서 · 4/6
C에는 언어 차원의 모듈이나 패키지 관리자가 없습니다. 대신 선언을 담은 헤더 파일(.h)과 구현을 담은 소스 파일(.c)을 나누고, 각각 따로 컴파일한 뒤 링커로 묶는 방식으로 프로그램을 나눕니다. 이 장에서는 헤더와 소스를 나누는 법, 분할 컴파일과 링크, 정적 · 공유 라이브러리, 그리고 Make와 CMake의 기본을 다룹니다.
헤더 파일에는 다른 파일이 알아야 하는 것, 즉 함수 프로토타입, 타입 정의, 상수만 둡니다. 함수 본문은 소스 파일에 둡니다. 같은 헤더가 한 번역 단위에 두 번 포함되어도 문제가 없도록 인클루드 가드를 씁니다.
// mathutil.h
#ifndef MATHUTIL_H
#define MATHUTIL_H
#include <stddef.h>
typedef struct {
double min, max, mean;
} Stats;
int clamp(int value, int lo, int hi);
Stats compute_stats(const double *values, size_t count);
#endif // MATHUTIL_H// mathutil.c
#include "mathutil.h"
static double sum(const double *values, size_t count) { // 이 파일 안에서만 보임
double total = 0.0;
for (size_t i = 0; i < count; i++) total += values[i];
return total;
}
int clamp(int value, int lo, int hi) {
if (value < lo) return lo;
if (value > hi) return hi;
return value;
}
Stats compute_stats(const double *values, size_t count) {
Stats s = { values[0], values[0], 0.0 }; // count >= 1 이라고 가정
for (size_t i = 1; i < count; i++) {
if (values[i] < s.min) s.min = values[i];
if (values[i] > s.max) s.max = values[i];
}
s.mean = sum(values, count) / (double)count;
return s;
}// main.c
#include <stdio.h>
#include "mathutil.h"
int main(void) {
double data[] = {3.0, 9.5, 1.5, 6.0};
Stats s = compute_stats(data, sizeof data / sizeof data[0]);
printf("min %.1f, max %.1f, mean %.2f\n", s.min, s.max, s.mean);
printf("clamp: %d\n", clamp(120, 0, 100));
return 0;
}몇 가지 규칙을 기억합니다.
#include <...>는 시스템 헤더, #include "..."는 프로젝트 헤더에 씁니다.static을 붙이면 그 파일 안에서만 보이는 내부 함수가 됩니다. 외부에 공개하지 않을 함수는 static으로 숨깁니다.extern int counter;로 선언하고, 소스 파일 하나에서만 int counter = 0;으로 정의합니다.#pragma once는 표준은 아니지만 주요 컴파일러가 모두 지원하는 인클루드 가드 대용입니다.소스 파일마다 -c로 오브젝트 파일을 만든 뒤 한꺼번에 링크합니다. 한 파일만 고치면 그 파일만 다시 컴파일하면 됩니다.
gcc -std=c17 -Wall -Wextra -c mathutil.c -o mathutil.o
gcc -std=c17 -Wall -Wextra -c main.c -o main.o
gcc main.o mathutil.o -o stats
./statsmathutil.o를 빼고 링크하면 "undefined reference to compute_stats" 같은 링크 오류가 납니다. 컴파일 단계는 헤더의 선언만 보고 통과했지만, 링커가 실제 정의를 찾지 못한 것입니다. 반대로 같은 함수를 두 소스 파일에서 정의하면 "multiple definition" 오류가 납니다.
여러 오브젝트 파일을 묶어 라이브러리로 만들면 다른 프로그램에서 재사용하기 쉽습니다.
| 종류 | Linux | macOS | Windows | 특징 |
|---|---|---|---|---|
| 정적 | .a | .a | .lib | 링크할 때 실행 파일 안에 복사됨 |
| 공유 | .so | .dylib | .dll | 실행할 때 불러옴, 여러 프로그램이 공유 |
# 정적 라이브러리
ar rcs libmathutil.a mathutil.o
gcc main.o -L. -lmathutil -o stats_static
# 공유 라이브러리 (Linux)
gcc -fPIC -c mathutil.c -o mathutil.pic.o
gcc -shared mathutil.pic.o -o libmathutil.so
gcc main.o -L. -lmathutil -Wl,-rpath,'$ORIGIN' -o stats_shared-L은 라이브러리를 찾을 디렉터리, -lmathutil은 libmathutil.a 또는 libmathutil.so를 뜻합니다. GCC 링커는 왼쪽에서 오른쪽으로 기호를 찾으므로 라이브러리는 그것을 쓰는 오브젝트 파일보다 뒤에 적습니다. 공유 라이브러리는 실행할 때도 찾을 수 있어야 하므로 rpath를 넣거나 표준 라이브러리 경로에 설치합니다.
파일이 늘어나면 명령을 손으로 치는 대신 빌드 도구를 씁니다. Make는 "대상: 의존 파일"과 명령(레시피)으로 규칙을 적고, 바뀐 파일만 다시 빌드합니다. 레시피 줄은 반드시 탭으로 들여 써야 합니다.
CC = gcc
CFLAGS = -std=c17 -Wall -Wextra -g
OBJS = main.o mathutil.o
stats: $(OBJS)
$(CC) $(OBJS) -o $@
%.o: %.c mathutil.h
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f stats $(OBJS)
.PHONY: clean$@는 대상 이름, $<는 첫 번째 의존 파일입니다. make로 빌드하고 make clean으로 결과물을 지웁니다.
CMake는 빌드 파일을 직접 실행하지 않고 Make, Ninja, Visual Studio 같은 빌드 시스템용 파일을 만들어 주는 도구입니다. 운영체제마다 다른 컴파일러 옵션을 대신 처리해 주므로 여러 플랫폼을 지원할 때 편합니다.
cmake_minimum_required(VERSION 3.16)
project(stats C)
set(CMAKE_C_STANDARD 17)
set(CMAKE_C_STANDARD_REQUIRED ON)
add_library(mathutil STATIC mathutil.c)
target_include_directories(mathutil PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
add_executable(stats main.c)
target_link_libraries(stats PRIVATE mathutil)cmake -S . -B build
cmake --build build
./build/statsadd_library의 STATIC을 SHARED로 바꾸면 공유 라이브러리가 됩니다. 소스와 분리된 build 디렉터리에 결과물을 두므로 소스 트리가 깨끗하게 유지됩니다.
static으로 숨깁니다.-c로 컴파일하고 마지막에 링크합니다. "undefined reference"는 링크 단계의 오류입니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.