使用Makefile进行编译和链接
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 <stdio.h>
#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 <stdio.h>
#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

浙公网安备 33010602011771号