본문으로 건너뛰기
Previous
Next
C++ Makefile Tutorial | Variables· Pattern Rules

C++ Makefile Tutorial | Variables· Pattern Rules

C++ Makefile Tutorial | Variables· Pattern Rules

이 글의 핵심

Makefile guide for C++ projects: tabs, automatic variables, wildcards, -MMD dependencies, parallel -j, and when to prefer CMake for cross-platform builds.

Entering

Makefile is the build automation script used by Make. Increase development productivity by automating compilation and linking.

1. Makefile basics

The simplest Makefile

# Makefile
myapp: main.cpp
	g++ main.cpp -o myapp
clean:
	rm -f myapp
# build
make
# organize
make clean

output of power:

g++ main.cpp -o myapp

Basic grammar

# Target: Dependency
# Command (be sure to start with a tab!)
target: dependencies
	command
# example
main.o: main.cpp
	g++ -c main.cpp -o main.o

component:

  • target: Name of the file or task to be created
  • Dependencies: Files needed to create the target
  • command: Shell command to execute (must start with tab)

2. Using variables

Basic variables

# variable definition
CXX = g++
CXXFLAGS = -std=c++17 -Wall -O2
INCLUDES = -I./include
LIBS = -lpthread -lm
# Use variables
myapp: main.cpp
	$(CXX) $(CXXFLAGS) $(INCLUDES) main.cpp -o myapp $(LIBS)
clean:
	rm -f myapp
.PHONY: clean

Automatic variables

CXX = g++
CXXFLAGS = -std=c++17 -Wall
# automatic variable
# $@: target name
# $<: first dependency
# $^: all dependencies
myapp: main.o util.o
	$(CXX) $^ -o $@
	# $^ = main.o util.o
	# $@ = myapp
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@
# $< = main.cpp (first dependency)
# $@ = main.o (target)

Automatic variable cleanup

variablemeaningExample
$@target namemyapp
$<first dependencymain.cpp
$^All dependenciesmain.o util.o
$?Newer dependency than targetmain.o

3. Practical example

Example 1: Simple project

# Compiler settings
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra
# target
TARGET = myapp
# build
$(TARGET): main.cpp
	$(CXX) $(CXXFLAGS) main.cpp -o $(TARGET)
# execution
run: $(TARGET)
	./$(TARGET)
# organize
clean:
	rm -f $(TARGET)
# non-file target
.PHONY: clean run

How ​​to use:

make # build
make run # run after build
make clean # cleanup

Example 2: Multiple file project

CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra
TARGET = myapp
# object file
OBJS = main.o calculator.o utils.o
# linking
$(TARGET): $(OBJS)
	$(CXX) $(OBJS) -o $(TARGET)
# compile
main.o: main.cpp calculator.h utils.h
	$(CXX) $(CXXFLAGS) -c main.cpp
calculator.o: calculator.cpp calculator.h
	$(CXX) $(CXXFLAGS) -c calculator.cpp
utils.o: utils.cpp utils.h
	$(CXX) $(CXXFLAGS) -c utils.cpp
# organize
clean:
	rm -f $(OBJS) $(TARGET)
.PHONY: clean

Example 3: Pattern Rules (Automation)

CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra
TARGET = myapp
# Find all .cpp files
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o)
# linking
$(TARGET): $(OBJS)
	$(CXX) $^ -o $@
# Pattern rule: all .cpp → .o
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
	rm -f $(OBJS) $(TARGET)
.PHONY: clean

Pattern Rule Description:

  • %.o: %.cpp: Compile all .cpp files as .o
  • $<: first dependency (.cpp file)
  • $@: target (.o file)

Example 4: Library linking

CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra
INCLUDES = -I./include
LDFLAGS = -lpthread -lm
TARGET = myapp
SRCS = $(wildcard src/*.cpp)
OBJS = $(SRCS:src/%.cpp=obj/%.o)
# linking
$(TARGET): $(OBJS)
	$(CXX) $^ -o $@ $(LDFLAGS)
# compile
obj/%.o: src/%.cpp
	@mkdir -p obj
	$(CXX) $(CXXFLAGS) $(INCLUDES) -c $< -o $@
clean:
	rm -rf obj $(TARGET)
.PHONY: clean

4. Advanced features

Conditional compilation

CXX = g++
TARGET = myapp
# Change flags according to DEBUG variable
ifeq ($(DEBUG),1)
    CXXFLAGS = -std=c++17 -Wall -g -DDEBUG
else
    CXXFLAGS = -std=c++17 -Wall -O2 -DNDEBUG
endif
$(TARGET): main.cpp
	$(CXX) $(CXXFLAGS) main.cpp -o $(TARGET)
clean:
	rm -f $(TARGET)
.PHONY: clean

How ​​to use:

make # Release build
make DEBUG=1 # Debug build

Using the ### function

CXX = g++
CXXFLAGS = -std=c++17 -Wall
# wildcard: file pattern matching
SRCS = $(wildcard src/*.cpp)
# patsubst: pattern substitution
OBJS = $(patsubst src/%.cpp,obj/%.o,$(SRCS))
# shell: Execute shell command
$(shell mkdir -p obj)
myapp: $(OBJS)
	$(CXX) $^ -o $@
obj/%.o: src/%.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
	rm -rf obj myapp
.PHONY: clean

Automatic creation of dependencies

CXX = g++
CXXFLAGS = -std=c++17 -Wall
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:.cpp=.o)
DEPS = $(OBJS:.o=.d)
myapp: $(OBJS)
	$(CXX) $^ -o $@
# -MMD: Create dependency files
%.o: %.cpp
	$(CXX) $(CXXFLAGS) -MMD -c $< -o $@
# Include dependency files
-include $(DEPS)
clean:
	rm -f $(OBJS) $(DEPS) myapp
.PHONY: clean

explanation:

  • -MMD: Create header dependencies as .d files.
  • -include: Include dependency files (no error occurs even without them)
  • Automatically recompiles when header file changes

5. Frequently occurring problems

Issue 1: Tabs vs Spaces

# ❌ Use of space (error!)
target:
    command
# ✅ Use tabs
target:
	command

Error Message:

Makefile:2: *** missing separator. Stop.

Solution:

  • Disable converting tabs to spaces in the editor settings.
  • Use Makefile mode (automatically insert tabs)

Issue 2: Missing dependencies

# ❌ No header dependency
main.o: main.cpp
	g++ -c main.cpp
# Even if util.h is changed, it will not be recompiled!
# ✅ Include header
main.o: main.cpp util.h config.h
	g++ -c main.cpp
# Automatic recompilation when util.h or config.h changes

Issue 3: Missing .PHONY

# ❌ If there is a file called clean, it will not be executed.
clean:
	rm -f *.o
# ✅ Use .PHONY
.PHONY: clean
clean:
	rm -f *.o
# Always runs even if there is a clean file

Issue 4: Parallel builds

# Sequential build (slow)
make
# Parallel build (4 tasks simultaneously)
make -j4
# Parallel build as many CPU cores
make -j$(nproc)

Practical Tips:

  • Reduce compilation time with parallel builds
  • Parallel build is possible only when dependencies are set correctly

6. Practical example: complete project

Project structure

project/
├── Makefile
├──include/
│ ├── calculator.h
│ └── utils.h
├── src/
│ ├── main.cpp
│ ├── calculator.cpp
│ └── utils.cpp
└── obj/
    └── (Created at build time)

Complete Makefile

# Compiler settings
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra
INCLUDES = -I./include
LDFLAGS = -lpthread
# directory
SRC_DIR = src
OBJ_DIR = obj
INC_DIR = include
# file
SRCS = $(wildcard $(SRC_DIR)/*.cpp)
OBJS = $(patsubst $(SRC_DIR)/%.cpp,$(OBJ_DIR)/%.o,$(SRCS))
DEPS = $(OBJS:.o=.d)
# target
TARGET = myapp
# Debug build
ifeq ($(DEBUG),1)
    CXXFLAGS += -g -DDEBUG
else
    CXXFLAGS += -O2 -DNDEBUG
endif
# Default target
all: $(TARGET)
# linking
$(TARGET): $(OBJS)
	$(CXX) $^ -o $@ $(LDFLAGS)
@echo "Build completed: $(TARGET)"
# Compile (automatically generate dependencies)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
	@mkdir -p $(OBJ_DIR)
	$(CXX) $(CXXFLAGS) $(INCLUDES) -MMD -c $< -o $@
# Include dependency files
-include $(DEPS)
# execution
run: $(TARGET)
	./$(TARGET)
# organize
clean:
	rm -rf $(OBJ_DIR) $(TARGET)
# Full rebuild
rebuild: clean all
# help
help:
@echo "Available targets:"
@echo " make - Release build"
@echo " make DEBUG=1 - Debug build"
@echo " make run - run after build"
@echo " make clean - cleanup"
@echo " make rebuild - full rebuild"
@echo " make -j4 - Parallel builds (4)"
.PHONY: all run clean rebuild help

Use example:

make # Release build
make DEBUG=1 # Debug build
make -j4 # parallel build
make run # run
make clean # cleanup
make rebuild # rebuild
make help # help

organize

Key takeaways

  1. Basic syntax: target, dependency, command
  2. Variables: CXX, CXXFLAGS, automatic variables ($@, $<, $^)
  3. Pattern Rule: Automate with %.o: %.cpp
  4. Function: File processing with wildcard, patsubst
  5. Automatically create dependencies: Manage header dependencies with -MMD

Makefile vs CMake

FeaturesMakefileCMake
ComplexitylowHigh
cross platformLIMITEDExcellent
learning curvegentleSteep
Direct controlHighlow
suitable projectsmall projectbig project

Practical tips

  1. Efficient Build
  • Enable parallel build (make -j4)
  • Automatic creation of dependencies (-MMD)
  • Compilation caching with ccache
  1. Maintenance
  • Centralize settings with variables
  • Clarify target with .PHONY
  • Explain complex rules with comments
  1. Debugging
  • make -n: Prints only commands (does not execute)
  • make -d: Print debug information
  • Check the value of the @echo variable

Make command

commandDescription
makeBuild default target
make cleanrun clean target
make -j44 task parallel build
make -nOutput only commands (dry-run)
make -Bforce rebuild all targets

Next steps



자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Makefile guide for C++ projects: tabs, automatic variables, wildcards, -MMD dependencies, parallel -j, and when to prefe… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, Makefile, Make, Build, GNU Make 등으로 검색하시면 이 글이 도움이 됩니다.