Makefile的基本结构:
目标:要生成的文件或执行的伪目标
依赖:生成目标所需的文件或其他目标
命令:生成目标的具体shell命令
变量的定义与使用:
赋值运算
=:递归展开
:=:简单展开
?=:条件赋值
+=:追加赋值
使用变量
$(VAR)
${VAR}
自动变量:
$@:当前目标名(如 $(TARGET))
$<:第一个依赖文件(如 main.c)
$^:所有依赖文件(如 main.o functions.o)
$?:比目标更新的依赖文件
example:
$(TARGET): $(OBJS)
$(CC) -o $@ $^
生成指定TARGET
实现Makefile编译出二进制程序并执行
编写如下文件:
// functions.h
void hello ();
// functions.c
#include
#include "functions.h"
void hello () {
printf ("Hello, world!\n");
}
// main.c
#include "functions.h"
int main () {
hello ();
return 0;
}
现在编写Makefile实现多文件编译:
CC ?= gcc
CFLAGS ?= -std=c11 -Wall -Wextra -Wpedantic -Werror -g
LDFLAGS ?= -Wl,--as-needed -Wl,--no-undefined
OUTPUT_DIR ?= .
SRCS = main.c functions.c
OBJS = $(SRCS:.c=.o)
TARGET = $(OUTPUT_DIR)/hello
all: $(TARGET)
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(TARGET) $(OBJS)
CC指定编译器为GCC
CFLAGS指定编译选项
-std=c11:使用 C11 标准。
-Wall -Wextra -Wpedantic:启用所有警告
-Werror:将警告视为错误(严格模式)
-g:生成调试信息(用于 gdb 调试)
LDFLAGS链接选项
-Wl,--as-needed:只链接实际需要的库
-Wl,--no-undefined:禁止未定义的符号(避免运行时错误)
OUTPUT_DIR ?= .设置输出目录为当前目录
SRCS设置源文件列表(main.c 和 functions.c)
OBJS设置目标文件列表(.c 替换为 .o,即 main.o functions.o)
TARGET设置最终生成的可执行文件路径(./hello)
all是构建目标,执行make后构建$(TARGET)
如果 $(OUTPUT_DIR) 不存在,则创建它:
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
链接目标:
$(TARGET): $(OBJS)
$(CC) $(LDFLAGS) -o $@ $^
该命令等价于gcc -Wl,--as-needed -Wl,--no-undefined -o exercise-01 main.o functions.o
编译,将.c编译成.o:
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
最后执行make all完成编译
关键执行路径总结
make → all → $(TARGET)
$(TARGET) 需要 $(OBJS)
每个 .o 文件通过 %.o: %.c 规则生成
最后链接所有 .o 文件生成可执行文件
静态链接
写入测试代码
// test.c
#include
#include "functions.h"
void
test_hello ()
{
printf ("Running test_hello...\n");
hello ();
}
int
main ()
{
test_hello ();
printf ("All tests passed.\n");
return 0;
}
生成functions.a作为静态链接库,并且将该库和main.o链接到一起,生成二进制可执行文件,对test同理
CC ?= gcc
CFLAGS = -std=c11 -Wall -Wextra -Wpedantic -Werror -g
AR = ar
ARFLAGS = rcs
OUTPUT_DIR ?= .
SRCS = main.c
OBJS = $(SRCS:.c=.o)
LIB_SRC = functions.c
LIB_OBJ = $(LIB_SRC:.c=.o)
LIB = $(OUTPUT_DIR)/functions.a
TARGET = $(OUTPUT_DIR)/hello
TEST_SRCS = test.c
TEST_OBJS = $(TEST_SRCS:.c=.o)
TEST_TARGET = $(OUTPUT_DIR)/hello_test
all: $(TARGET)
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
$(LIB): $(LIB_OBJ) | $(OUTPUT_DIR)
$(AR) $(ARFLAGS) $@ $^
$(TARGET): $(OBJS) $(LIB) | $(OUTPUT_DIR)
$(CC) -o $@ $^ -L$(OUTPUT_DIR) -l:functions.a
$(TEST_TARGET): $(TEST_OBJS) $(LIB) | $(OUTPUT_DIR)
$(CC) -o $@ $^ -L$(OUTPUT_DIR) -l:functions.a
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
test: $(TEST_TARGET)
./$(TEST_TARGET)
clean:
rm -f $(TARGET) $(TEST_TARGET) $(OBJS) $(TEST_OBJS) $(LIB_OBJ) $(LIB)
.PHONY: all test clean
特别加入了静态库生成,将.o打包成.a
$(LIB): $(LIB_OBJ) | $(OUTPUT_DIR)
$(AR) $(ARFLAGS) $@ $^
将主程序链接为二进制文件:
$(TARGET): $(OBJS) $(LIB) | $(OUTPUT_DIR)
$(CC) -o $@ $^ -L$(OUTPUT_DIR) -l:functions.a
将测试程序链接为二进制文件:
$(TEST_TARGET): $(TEST_OBJS) $(LIB) | $(OUTPUT_DIR)
$(CC) -o $@ $^ -L$(OUTPUT_DIR) -l:functions.a
生成静态链接库
将程序编译成静态链接库而非二进制可执行文件,静态链接库需要被链接到最终的可执行文件中才能使用
CC ?= gcc
CFLAGS = -std=c11 -Wall -Wextra -Wpedantic -Werror -g
LDFLAGS = -Wl,--as-needed -Wl,--no-undefined
AR = ar
ARFLAGS = rcs
OUTPUT_DIR ?= .
SRCS = main.c
OBJS = $(SRCS:.c=.o)
TARGET = $(OUTPUT_DIR)/hello
LIB_NAME = libfunctions.a
LIB_TARGET = $(OUTPUT_DIR)/$(LIB_NAME)
LIB_SRC = functions.c
LIB_OBJ = $(LIB_SRC:.c=.o)
TEST_SRCS = test.c
TEST_OBJS = $(TEST_SRCS:.c=.o)
TEST_TARGET = $(OUTPUT_DIR)/hello_test
all: $(TARGET) test
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
$(LIB_OBJ): $(LIB_SRC) | $(OUTPUT_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(LIB_TARGET): $(LIB_OBJ) | $(OUTPUT_DIR)
$(AR) $(ARFLAGS) $@ $^
$(TARGET): $(OBJS) $(LIB_TARGET) | $(OUTPUT_DIR)
$(CC) $(LDFLAGS) -o $@ $(OBJS) -L$(OUTPUT_DIR) -lfunctions
$(TEST_TARGET): $(TEST_OBJS) $(LIB_TARGET) | $(OUTPUT_DIR)
$(CC) $(LDFLAGS) -o $@ $(TEST_OBJS) -L$(OUTPUT_DIR) -lfunctions
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
test: $(TEST_TARGET)
./$(TEST_TARGET)
clean:
rm -f $(LIB_TARGET) $(TARGET) $(TEST_TARGET) $(OBJS) $(LIB_OBJ) $(TEST_OBJS)
.PHONY: all test clean
关键点解析:
静态库编译:
$(LIB_OBJ): $(LIB_SRC) 规则将 functions.c 编译为 functions.o
$(LIB_TARGET): $(LIB_OBJ) 规则使用 ar 命令将 .o 文件打包为静态库 libfunctions.a
主程序链接:
$(TARGET) 链接 main.o 和静态库 libfunctions.a
使用 -L$(OUTPUT_DIR) 指定库搜索路径
使用 -lfunctions 链接库(注意省略了 lib 前缀和 .a 后缀)
测试程序链接:
测试程序直接链接 test.o 和静态库
注意测试程序不再直接编译 functions.c,而是使用静态库
目录处理:
所有生成文件的规则都依赖 | $(OUTPUT_DIR) 确保目录存在
清理规则:
新增了对静态库文件 $(LIB_TARGET) 的清理
构建流程说明:
编译 functions.c 为 functions.o
将 functions.o 打包为静态库 libfunctions.a
编译 main.c 为 main.o
链接 main.o 和静态库生成主程序
编译 test.c 为 test.o
链接 test.o 和静态库生成测试程序
生成动态链接库
CC ?= gcc
CFLAGS = -std=c11 -Wall -Wextra -Wpedantic -Werror -g -fPIC
LDFLAGS = -Wl,--as-needed -Wl,--no-undefined
OUTPUT_DIR ?= .
RPATH = -Wl,-rpath,'$$ORIGIN'
SRCS = main.c
OBJS = $(SRCS:.c=.o)
TARGET = $(OUTPUT_DIR)/hello
LIB_NAME = libfunctions.so
LIB_TARGET = $(OUTPUT_DIR)/$(LIB_NAME)
LIB_SRC = functions.c
LIB_OBJ = $(LIB_SRC:.c=.o)
TEST_SRCS = test.c
TEST_OBJS = $(TEST_SRCS:.c=.o)
TEST_TARGET = $(OUTPUT_DIR)/hello_test
.PHONY: all test clean
all: $(TARGET) test
$(OUTPUT_DIR):
mkdir -p $(OUTPUT_DIR)
$(LIB_OBJ): $(LIB_SRC) | $(OUTPUT_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(LIB_TARGET): $(LIB_OBJ) | $(OUTPUT_DIR)
$(CC) -shared $(LDFLAGS) -o $@ $^
$(TARGET): $(OBJS) | $(LIB_TARGET) $(OUTPUT_DIR)
$(CC) $(LDFLAGS) $(RPATH) -o $@ $(OBJS) -L$(OUTPUT_DIR) -lfunctions
$(TEST_TARGET): $(TEST_OBJS) | $(LIB_TARGET) $(OUTPUT_DIR)
$(CC) $(LDFLAGS) $(RPATH) -o $@ $(TEST_OBJS) -L$(OUTPUT_DIR) -lfunctions
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
test: $(TEST_TARGET)
LD_LIBRARY_PATH=$(OUTPUT_DIR) ./$(TEST_TARGET)
clean:
rm -f $(LIB_TARGET) $(TARGET) $(TEST_TARGET) $(OBJS) $(LIB_OBJ) $(TEST_OBJS)
特别使用了这段代码生成动态链接库:
$(LIB_TARGET): $(LIB_OBJ) | $(OUTPUT_DIR)
$(CC) -shared $(LDFLAGS) -o $@ $^
最终链接到一起:
$(TARGET): $(OBJS) | $(LIB_TARGET) $(OUTPUT_DIR)
$(CC) $(LDFLAGS) $(RPATH) -o $@ $(OBJS) -L$(OUTPUT_DIR) -lfunctions