Understanding Core Concepts and Pattern Rules in Makefile

Recently, I have been learning about the wildcard % in makefiles. At first, I found it quite confusing, but gradually I understood that it is fundamentally different from the wildcard * in Linux; it is more like a wildcard specifically designed for makefiles. Let’s break this down step by step.

First, let’s understand the core rules of makefiles: targets and dependencies.

target ... : prerequisites ...  command

This represents the dependency of a file: one or more target files depend on the files in prerequisites (to be generated), and the generation rules are defined in the command. In simpler terms:

If there is more than one file in prerequisites that is newer than the target file, the command defined will be executed.

Next, let’s take a simple example. Suppose our project structure is as follows:

project/├── main.c├── utils.c├── utils.h├── helper.c├── helper.h└── Makefile
The file dependencies are as follows:
main.exe depends on main.o utils.o helper.o main.o depends on main.c utils.h helper.h utils.o depends on utils.c utils.h helper.o depends on helper.c helper.h
We can write a makefile as follows:
# Final targetmain.exe: main.o utils.o helper.o	gcc main.o utils.o helper.o -o main.exe# Dependencies of main.omain.o: main.c utils.h helper.h	gcc -c main.c -o main.o# Dependencies of utils.outils.o: utils.c utils.h	gcc -c utils.c -o utils.o# Dependencies of helper.ohelper.o: helper.c helper.h	gcc -c helper.c -o helper.o
clean:		rm -f *.o main.exe.PHONY: clean
What happens when we execute the make command?
  1. The make.exe application will look for a file named “makefile” in the current directory.
  2. If found, it will take the first target file (main.exe) in the makefile as the final target file.
  3. If the main.exe file does not exist, make will first check whether the dependency files (<span>main.o utils.o helper.o</span>) exist or check if the modification times of the dependency files are newer than the target file.
  4. If the dependency files for main.exe do not exist or have been updated (for example, if helper.o is missing), then make will look for the dependencies of helper.o. If it finds the dependencies for this file (the dependency is<span>helper.o: helper.c helper.h</span>), it will generate the helper.o file according to that rule (the rule is<span>gcc -c helper.c -o helper.o</span>).
  5. Once the dependencies for the final target file (<span>main.exe: main.o utils.o helper.o</span>) are ready, the corresponding rules will be executed to generate the final target.

It is especially important to note step 4, where make starts from the final target and recursively checks each dependency to see if it needs to be rebuilt. If it confirms that a target file needs to be built, it will match the rules and execute the commands.

The dependencies in make are known:

main.exe: main.o utils.o helper.o    # Rule 1main.o: main.c utils.h helper.h      # Rule 2utils.o: utils.c utils.h             # Rule 3helper.o: helper.c helper.h          # Rule 4
The actual execution process is as follows:
Step 1: Make needs to build main.exe    Check dependencies of Rule 1: main.o utils.o helper.o    Directly jump to the need to build main.oStep 2: Build main.o    Make finds that main.o is needed, directly looks for the rule to build main.o (Rule 2)    Check dependencies of Rule 2: main.c utils.h helper.h    If these files exist and are newer than main.o, execute gcc -c main.c -o main.oStep 3: Build utils.o    Make returns to the dependency list of main.exe, the next is utils.o    Directly look for the rule to build utils.o (Rule 3)    Check dependencies and buildStep 4: Build helper.o    Similarly, directly look for Rule 4 and build
To summarize the above process:
# First build (all files do not exist)make# Execution order:1. Check main.o: does not exist → compile main.c → generate main.o2. Check utils.o: does not exist → compile utils.c → generate utils.o3. Check helper.o: does not exist → compile helper.c → generate helper.o4. Check main.exe: dependencies complete → link all .o → generate main.exe

Finally, let’s discuss the role of the wildcard % in make.

Modify the above makefile as follows:

# Compiler settingsCC = gccCFLAGS = -Wall# Source files and target filesSOURCES = main.c utils.c helper.cHEADERS = utils.h helper.hOBJECTS = $(SOURCES:.c=.o)TARGET = main.exe# Final target$(TARGET): $(OBJECTS)$(CC) $(OBJECTS) -o $@# Pattern rule: generate .o files from .c files%.o: %.c $(HEADERS)$(CC) $(CFLAGS) -c $< -o $@# Clean commandclean:		rm -f $(OBJECTS) $(TARGET).PHONY: clean
The dependencies under the above code are as follows:
main.exe    ├── main.o ─── main.c + all header files    ├── utils.o ── utils.c + all header files    └── helper.o ─ helper.c + all header files

Here, the wildcard % is used for string matching and replacement in pattern rules. It acts like a universal placeholder, representing “any string” during rule definition and being replaced by specific content during use.

Rule template: %.o: %.c is equivalent to: ______.o comes from ______.cWhen main.o is needed: main.o: main.cWhen utils.o is needed: utils.o: utils.c

When make needs to build an .o file, such as main.o, its working process is as follows:

# Make's thought process:1. I need main.o2. Look for a rule that generates main.o3. Found pattern rule: %.o: %.c4. Try to match: replace % with "main"5. Match successful! The dependency should be main.c6. Execute command: gcc -c main.c -o main.o

It is evident that the wildcard % allows the makefile to transform from “writing a rule for each file” to “writing a rule that handles all similar files”. In other words, the rule (or dependency) %.o: %.c is a declaration in the makefile, just like a function declaration, lying there waiting for another dependency to require this rule, at which point make will match it automatically. Therefore, some AI explanations say it is a passive string matching.If we simply understand the makefile as C language, then you won’t understand this code. When writing makefiles, you must step out of the conventional programming language mindset.

Leave a Comment