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
| 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
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
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Makefile guide for C++ projects: tabs, automatic variables, wildcards, -MMD dependencies, parallel -j, and when to.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- C++ 컴파일 과정 | 전처리·컴파일·어셈블·링킹 완벽 가이드
- C++ 링킹 완벽 가이드 | 정적/동적 링킹과 에러 해결 (undefined reference)
- C++ CMake | ‘빌드 시스템’ 초보자 가이드
Keywords Covered in This Article (Related Search Terms)
This article covers C++, Makefile, Make, Build, GNU Make.