C++ Makefile Tutorial | Variables, Pattern Rules, Dependencies & Parallel Make
이 글의 핵심
Hands-on Make for C++: compile/link recipes, debug vs release, parallel builds, and a full multi-directory example.
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
| variable | meaning | Example |
|---|---|---|
$@ | target name | myapp |
$< | first dependency | main.cpp |
$^ | All dependencies | main.o util.o |
$? | Newer dependency than target | main.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.cppfiles as.o$<: first dependency (.cppfile)$@: target (.ofile)
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.dfiles.-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
- Basic syntax: target, dependency, command
- Variables:
CXX,CXXFLAGS, automatic variables ($@,$<,$^) - Pattern Rule: Automate with
%.o: %.cpp - Function: File processing with
wildcard,patsubst - Automatically create dependencies: Manage header dependencies with
-MMD
Makefile vs CMake
| Features | Makefile | CMake |
|---|---|---|
| Complexity | low | High |
| cross platform | LIMITED | Excellent |
| learning curve | gentle | Steep |
| Direct control | High | low |
| suitable project | small project | big project |
Practical tips
- Efficient Build
- Enable parallel build (
make -j4) - Automatic creation of dependencies (
-MMD) - Compilation caching with ccache
- Maintenance
- Centralize settings with variables
- Clarify target with
.PHONY - Explain complex rules with comments
- Debugging
make -n: Prints only commands (does not execute)make -d: Print debug information- Check the value of the
@echovariable
Make command
| command | Description |
|---|---|
make | Build default target |
make clean | run clean target |
make -j4 | 4 task parallel build |
make -n | Output only commands (dry-run) |
make -B | force rebuild all targets |
Next steps
- C++ Compilation Process
- C++ Linking
- C++ CMake Build System
Related articles
- C++ CMake Complete Guide | Cross-platform build·latest CMake 3.28+ features·presets·modules
- C++ CMake |
- C++ CMake find_package complete guide | External library integration
- C++ CMake Targets Complete Guide | Target-based build system
- C++ Conan Complete Guide | Modern C++ package management