C++ Makefile Tutorial | Variables, Pattern Rules, Dependencies & Parallel Make

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

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

  • C++ Compilation Process
  • C++ Linking
  • C++ CMake Build System

  • 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