Press ESC to close

使用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

#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