본문으로 건너뛰기 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



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.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, Makefile, Make, Build, GNU Make.