Makefile - 5. Makefile 최적화 및 실전 프로젝트 적용 (5-1. 효율적인 Makefile 작성법 (Writing Efficient Makefiles))

2025. 3. 11. 19:13개발/개발도구와 환경

📌 5-1. 효율적인 Makefile 작성법 (Writing Efficient Makefiles)

Makefile을 효율적으로 작성하면 코드 중복을 줄이고 유지보수를 쉽게 할 수 있습니다.
이를 위해 공통 규칙 정리 (%.o: %.c 활용) 및 중복 제거 (define 사용) 기법을 적용합니다.


1. 공통 규칙 정리 (%.o: %.c 활용)

📌 1-1. %.o: %.c를 활용한 패턴 규칙 (Pattern Rules)

Makefile에서 .c 파일을 컴파일하여 .o 파일로 변환하는 과정은 반복적인 작업입니다.
이를 효율적으로 처리하기 위해 패턴 규칙(%.o: %.c)을 사용합니다.


📌 1-2. 기본적인 %.o: %.c 규칙

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@
자동 변수 설명
$< 첫 번째 의존 파일 (.c 파일)
$@ 현재 목표 파일 (.o 파일)

📌 1-3. %.o: %.c 활용한 Makefile 최적화

🔹 예제 1: 기본적인 %.o: %.c 활용

CC = gcc
CFLAGS = -Wall -g
SRC = main.c utils.c io.c
OBJS = $(SRC:.c=.o)
TARGET = myprogram

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJS) $(TARGET)

이점

  • 모든 .c 파일을 자동으로 .o 파일로 변환.
  • 새로운 .c 파일이 추가되면 Makefile을 수정할 필요 없음.
  • 반복적인 규칙을 제거하여 가독성을 높임.

📌 1-4. 다중 디렉토리에서 %.o: %.c 활용

🔹 예제 2: 서브 디렉토리에서 %.o: %.c 적용

CC = gcc
CFLAGS = -Wall -g
SRCDIR = src
OBJDIR = build
SRC = $(wildcard $(SRCDIR)/*.c)
OBJS = $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(SRC))
TARGET = myprogram

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

$(OBJDIR)/%.o: $(SRCDIR)/%.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -f $(OBJDIR)/*.o $(TARGET)

이점

  • src/ 디렉토리의 .c 파일을 자동으로 찾아 build/ 디렉토리에 .o 파일로 변환.
  • 디렉토리 구조를 유지하면서 빌드 관리가 편리해짐.

2. 중복 제거 (define 사용)

📌 2-1. define을 활용한 코드 중복 제거

Makefile에서 define을 사용하면 여러 줄의 명령어를 변수처럼 저장하여 재사용할 수 있음.

define compile_template
	$(CC) $(CFLAGS) -c $< -o $@
endef

➡ 위 define 블록을 활용하면 여러 줄의 코드를 한 줄처럼 사용할 수 있음.


📌 2-2. define을 활용한 최적화된 Makefile

🔹 예제 3: define을 사용한 코드 중복 제거

CC = gcc
CFLAGS = -Wall -g
SRC = main.c utils.c io.c
OBJS = $(SRC:.c=.o)
TARGET = myprogram

define compile_template
	$(CC) $(CFLAGS) -c $< -o $@
endef

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

%.o: %.c
	$(compile_template)

clean:
	rm -f $(OBJS) $(TARGET)

이점

  • define compile_template를 사용하여 컴파일 명령을 재사용 가능.
  • .c → .o 변환 과정에서 불필요한 코드 반복 제거.
  • 가독성이 향상되며 유지보수 용이.

📌 2-3. define을 활용한 복잡한 작업 자동화

🔹 예제 4: define을 사용하여 디버그 빌드 지원

CC = gcc
CFLAGS = -Wall -g
SRC = main.c utils.c io.c
OBJS = $(SRC:.c=.o)
TARGET = myprogram

define debug_template
	$(CC) $(CFLAGS) -DDEBUG -c $< -o $@
endef

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

debug: CFLAGS += -DDEBUG
debug: $(OBJS)
	$(CC) $(CFLAGS) -o $(TARGET) $^

%.o: %.c
	$(debug_template)

clean:
	rm -f $(OBJS) $(TARGET)

이점

  • make debug를 실행하면 디버그 빌드(-DDEBUG 플래그 포함) 수행.
  • define을 사용하여 디버그 빌드와 일반 빌드를 쉽게 전환 가능.

3. define과 $(eval)을 활용한 동적 Makefile 생성

📌 3-1. $(eval)을 활용한 동적 빌드 시스템

Makefile에서 $(eval)을 사용하면 실행 중에 새로운 규칙을 생성 가능.

🔹 예제 5: $(eval)을 사용하여 자동 규칙 생성

CC = gcc
CFLAGS = -Wall -g
SRC = main.c utils.c io.c
OBJS = $(SRC:.c=.o)
TARGET = myprogram

define compile_rule
$(1): $(1:.o=.c)
	$(CC) $(CFLAGS) -c $$< -o $$@
endef

$(foreach obj,$(OBJS),$(eval $(call compile_rule,$(obj))))

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $@ $^

clean:
	rm -f $(OBJS) $(TARGET)

이점

  • $(foreach obj, $(OBJS), $(eval $(call compile_rule, $(obj))))을 사용하여 각 .o 파일에 대한 규칙을 자동 생성.
  • 새로운 .c 파일을 추가해도 Makefile을 수정할 필요 없음.

📌 5-1. 효율적인 Makefile 작성법 요약 정리

기법  설명
%.o: %.c 패턴 규칙 .c 파일을 .o 파일로 변환하는 공통 규칙
디렉토리 관리 (patsubst) src/ 디렉토리에서 .c를 .o로 자동 변환
define 활용 중복된 명령어를 변수처럼 저장하여 재사용
$(eval) 활용 실행 중에 새로운 규칙을 동적으로 생성