개요
Makefile에서 CFLAGS와 LDFLAGS는 각각 컴파일 단계와 링크 단계에 필요한 옵션을 전달하는 관례적인 변수입니다. 이 두 변수를 적절히 활용하면 빌드 과정을 체계적으로 제어할 수 있습니다.
CFLAGS 상세
CFLAGS(Compiler Flags)는 소스 파일(.c)을 목적 파일(.o)로 변환하는 컴파일 단계에서 사용되는 옵션들을 담당합니다.
주요 옵션
| 옵션 | 설명 |
|---|---|
-I<경로> | 헤더 파일 검색 경로 지정 |
-Wall | 주요 경고 메시지 활성화 |
-Werror | 경고를 오류로 처리하여 빌드 중단 |
-O2 | 중간 수준 최적화 적용 |
-g | 디버깅 정보 포함 |
-c | 컴파일만 수행하고 링크는 생략 |
-D<매크로> | 전처리기 매크로 정의 |
-I: 헤더 경로 지정
다음과 같은 프로젝트 구조를 가정합니다:
demo/
├── main.c
└── header/
└── config.h
main.c에서 config.h를 포함하려면 헤더 경로를 명시해야 합니다:
// main.c
#include <stdio.h>
#include "config.h"
int main(void)
{
printf("VERSION: %d\n", BUILD_VER);
return 0;
}
// header/config.h
#ifndef CONFIG_H
#define CONFIG_H
#define BUILD_VER 42
#endif
Makefile 예시:
TOOLCHAIN := gcc
SOURCE := main.c
OBJECT := $(SOURCE:.c=.o)
BUILDFLAGS := -I./header # 헤더 검색 경로 추가
.PHONY: build tidy
build: app
@echo "빌드 완료"
app: $(OBJECT)
$(TOOLCHAIN) -o $@ $^
%.o: %.c
$(TOOLCHAIN) $(BUILDFLAGS) -c $< -o $@
tidy:
rm -f $(OBJECT) app
-Wall과 -Werror
-Wall은 코드의 잠재적 문제를 탐지하여 경고로 출력합니다. 단, 경고만으로는 빌드를 중단하지 않습니다.
// warn.c
#include <stdio>
int global_val; // 미사용 전역 변수
int main(void)
{
printf("warning test\n");
return 0;
}
위 코드를 -Wall 옵션으로 컴파일하면 미사용 변수에 대한 경고가 출력됩니다. -Werror를 추가하면 이 경고가 오류로 처리되어 빌드가 중단됩니다:
BUILDFLAGS := -Wall -Werror # 경고 발생 시 빌드 실패
-O2 최적화
-O2는 컴파일 시간과 실행 성능 사이의 균형을 맞추는 중간 수준 최적화입니다. 적용되는 주요 기법은 다음과 같습니다:
- 상수 폴딩: 컴파일 시점에 계산 가능한 표현식을 상수로 대체
- 루프 최적화: 루프 전개, 불필요한 반복 제거
- 함수 인라인화: 작은 함수를 호출 지점에 직접 삽입
- 죽은 코드 제거: 도달 불가능한 코드 록 제거
| 최적화 레벨 | 특징 |
|---|---|
-O0 | 최적화 없음, 디버깅에 유리 |
-O1 | 기본 최적화, 빠른 컴파일 |
-O2 | 권장 레벨, 성능과 안정성 균형 |
-O3 | 공격적 최적화, 잠재적 부작용 |
-Os | 코드 크기 최소화, 임베디드용 |
-g 디버깅 정보
-g 옵션은 소스 코드의 줄 번호, 변수명, 자료형 등의 정보를 바이너리에 포함시켜 gdb 등의 디버거에서 소스 레벨 디버깅이 가능하도록 합니다. 디버깅 시에는 -O0 -g 조합을 권장합니다.
-c: 객체 파일 생성
링크 과정 없이 목적 파일만 생성할 때 사용합니다. Makefile의 패턴 규칙에서 필수적으로 활용됩니다.
-D: 조건보 컴파일
소스 코드를 수정하지 않고 빌드 시점에 매크로를 정의할 수 있습니다:
// feature.c
#include <stdio.h>
int main(void)
{
printf("mode: ");
#if defined(PROFILE)
printf("profiling enabled\n");
#elif defined(PRODUCTION)
printf("production release\n");
#else
printf("development\n");
#endif
return 0;
}
# Makefile
BUILDFLAGS := -DPROFILE # 또는 -DPRODUCTION
%.o: %.c
$(TOOLCHAIN) $(BUILDFLAGS) -c $< -o $@
LDFLAGS 상세
LDFLAGS(Linker Flags)는 목적 파일들을 최종 실행 파일이나 라이브러리로 결합하는 링크 단계에서 사용됩니다.
주요 션
| 옵션 | 설명 |
|---|---|
-L<경로> | 라이브러리 파일 검색 경로 지정 |
-l<이름> | 링크할 라이브러리 지정 |
-Wl,-rpath=<경로> | 실행 시 동적 라이브러리 검색 경로 |
-static | 정적 링크 강제 |
-L과 -l의 협력
사용자 정의 라이브러리를 링크할 때는 경로와 라이브러리 이름을 함께 지정합니다:
# 프로젝트 구조
# myapp/
# ├── calc.c
# └── lib/
# └── libcalc.a
LINK_PATHS := -L./lib
LINK_LIBS := -lcalc
app: calc.o
$(TOOLCHAIN) -o $@ $^ $(LINK_PATHS) $(LINK_LIBS)
-lcalc는 libcalc.so 또는 libcalc.a를 찾아 링크합니다. 동적 라이브러리를 우선 시도하며, 없으면 정적 라이브러리를 사용합니다.
-Wl: 링커 직접 제어
GCC를 통해 링커(ld)에 전용 옵션을 전달할 때 사용합니다:
RUNTIME_PATH := -Wl,-rpath,'$$ORIGIN/lib'
app: main.o
$(TOOLCHAIN) -o $@ $^ -L./lib -lhelper $(RUNTIME_PATH)
위 예시는 실행 파일이 자신의 위치 기준 lib 디렉토리에서 동적 라이브러리를 검색하도록 설정합니다.
-static
모든 의존성을 실행 파일에 내장하여 외부 동적 라이브러리 없이도 실행 가능한 독립 바이너리를 생성합니다:
LINKFLAGS := -static
app: $(OBJECTS)
$(TOOLCHAIN) $(LINKFLAGS) -o $@ $^ -lm
CFLAGS와 LDFLAGS 비교
| 구분 | CFLAGS | LDFLAGS |
|---|---|---|
| 적용 단계 | 컴파일 (.c → .o) | 링크 (.o → 실행 파일) |
| 처리 대상 | 소스 파일, 헤더 파일 | 목적 파일, 라이브러리 |
| 대표 옵션 | -I, -Wall, -O2, -g | -L, -l, -Wl |
| 관련 도구 | 컴파일러 (gcc, clang) | 링커 (ld) |