Makefile Rules

Dependencies

The Makefile uses dependencies to automatically determine whether a file needs to be recompiled. For example, <span>main.o: main.c</span>, when <span>main.c</span> changes, <span>main.o</span> will be recompiled, and then relinked to generate the target file <span>app</span>.

main.o: main.c    $(CC) $(CFLAGS) -c main.c -o main.o
app: main.o    $(CC) $(CFLAGS) main.o -o app

Pattern Rules

Using the wildcard <span>%</span> represents a class of files, such as <span>%.o: %.c</span> which indicates that all <span>.c</span> files are compiled into their corresponding <span>.o</span> files, simplifying the writing of Makefiles and avoiding duplicate rules.

%.o represents all files ending with .o, representing the first file in the dependency list, < represents the first file in the dependency list, and @ represents the target file.

%.o: %.c    $(CC) $(CFLAGS) -c $&lt; -o $@
app: main.o func.o    $(CC) $(CFLAGS) main.o func.o -o app

Built-in Functions

The Makefile provides various built-in functions, such as <span>wildcard</span> for matching files, <span>patsubst</span> for pattern replacement, and <span>subst</span> for string replacement, which help in handling filenames, generating commands, etc.

(objects) represents all filenames in the variable objects, rm−f(objects) app means deleting all .o files and the app file.

objects = main.o func.o
app: $(objects)    $(CC) $(CFLAGS) $(objects) -o app
clean:    rm -f $(objects) app

Implicit Rules

The Makefile has built-in implicit rules that automatically select the compiler and commands based on file extensions. For example, <span>.c</span> files will be compiled into <span>.o</span> files using <span>cc</span>, reducing the need for explicit command writing.

$^ represents all filenames in the dependency list. The Makefile will automatically select the correct compiler based on the file extension and generate the corresponding target file.

app: main.c func.c    $(CC) $(CFLAGS) $^ -o $@

Phony Targets

When using RM, if an error occurs with make clean: make: ‘clean’ is up to date, you need to add a phony directive: .PHONY: clean or .PHONY: $(C_CLEAN), where C_CLEAN is a variable.

Variables

The Makefile supports the definition and referencing of variables. Variables can help simplify the writing of Makefiles. The format for defining a variable is: VARNAME = value. For example:

CC = gcc
CFLAGS = -Wall -O2

The format for referencing a variable is: $(VARNAME). For example:

$(CC) $(CFLAGS) -c main.c -o main.o

Custom Variables

Numbers, letters, underscores, cannot start with a number, case-sensitive, do not use # when referencing variables, need to add $ when accessing, if unsuccessful, add () around the variable, spaces are allowed around the equals sign, all variable types are strings, no need for "" when using.

System Variable Environment

CC: the name of the compiler, default is gcc, CC = arm-linux-gcc
RM: remove files, equivalent to rm -f

Automatic Variables

$^: represents all dependency files
$@: represents the target file

Multi-directory Projects

Source file directory src, place *.c files

Header file directory include, place *.h files

Library file directory lib, place *.so files

Executable file bin, the final generated binary

gcc ./src/*.c -o main -I ./include/ -lm, -lm is required for sqrt, generally can write -I for header file linking, capital I -L for library directory linking -l for library access, lowercase l.

C_SRC = $(wildcard ./src/*.c)
#C_OBJ = $(patsubst %c,%o,$(C_SRC))   ,# is a comment
I_DIR = -I ./include
L_DIR = -L ./lib -lm
C_BIN = ./bin/main
C_CLEAN = clean

$(C_BIN): $(C_OBJ)    $(CC) $^ -o $@ $(I_DIR) $(L_DIR)
.PHONY: $(C_CLEAN)
$(C_CLEAN):    $(RM) $(C_BIN)

Complex Files

(wildcard) function: searches for matching files in a given path, usage (wildcard arg1,arg2,arg3)

patsubst function: converts all *.c files to *.o files, usage $(patsubst%c,%o, dependency files)

Library Creation

Static libraries (<span>.a</span>) package multiple object files, created using the <span>ar</span> command, linked to the program at compile time, and do not require the library file at runtime. Dynamic libraries (<span>.so</span>) are loaded at runtime, and the system must be able to find the library file.

Libraries are stored in binary form and need to be linked during compilation.

Static library lib*.a, libadd.a

During compilation, static linking is required to include library functions in the executable program.

After compilation, the static library is not needed for execution.

Dynamic library lib*.so, linadd.so

During program compilation, no linking is done, and when functions are needed, they are handled by the library at runtime, returning results.

Steps to Create a Static Library

Main function test data: main.c

Implementation of functional functions: add.c, add.h

Compile functional functions into a library file (binary file)

Compile functional files *.c into *.o files

gcc add.c -o add.o -c

Pack *.o files into an .a file, the filename must start with lib

ar rcs libadd.a add.o

Combined Compilation

gcc main.c libadd.a -o main

Test ./main

Steps to Create a Dynamic Library

Main function test data: main.c

Implementation of functional functions: add.c, add.h

Compile functional functions into a library file (binary file)

Compile functional files *.c into *.o files

gcc add.c -o add.o -fPIC -c

Pack *.o files into a .so file, the filename must start with lib

gcc -shared -fPIC -o libadd.so add.o

Combined Compilation

gcc main.c -o main -L -ladd

Test ./main

Dynamic Library Errors

./main: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory
Solution: Copy the corresponding dynamic library to the lib directory in the Linux system
sudo cp libadd.so /lib

Conclusion

The Makefile efficiently manages the compilation process through dependencies and rules, with variables, functions, and implicit rules simplifying the writing process. Phony targets are used for cleanup operations. Multi-directory projects require attention to file references, and library creation is divided into static and dynamic, with dynamic libraries needing correct path settings. The Makefile is an efficient tool for software development, and proper use can optimize the compilation process and improve development efficiency.

Leave a Comment