使用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.cfunctions.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完成编译

关键执行路径总结

  1. makeall$(TARGET)
  2. $(TARGET) 需要 $(OBJS)
  3. 每个 .o 文件通过 %.o: %.c 规则生成
  4. 最后链接所有 .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

关键点解析:

  1. 静态库编译
    • $(LIB_OBJ): $(LIB_SRC) 规则将 functions.c 编译为 functions.o
    • $(LIB_TARGET): $(LIB_OBJ) 规则使用 ar 命令将 .o 文件打包为静态库 libfunctions.a
  2. 主程序链接
    • $(TARGET) 链接 main.o 和静态库 libfunctions.a
    • 使用 -L$(OUTPUT_DIR) 指定库搜索路径
    • 使用 -lfunctions 链接库(注意省略了 lib 前缀和 .a 后缀)
  3. 测试程序链接
    • 测试程序直接链接 test.o 和静态库
    • 注意测试程序不再直接编译 functions.c,而是使用静态库
  4. 目录处理
    • 所有生成文件的规则都依赖 | $(OUTPUT_DIR) 确保目录存在
  5. 清理规则
    • 新增了对静态库文件 $(LIB_TARGET) 的清理

构建流程说明:

  1. 编译 functions.cfunctions.o
  2. functions.o 打包为静态库 libfunctions.a
  3. 编译 main.cmain.o
  4. 链接 main.o 和静态库生成主程序
  5. 编译 test.ctest.o
  6. 链接 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
posted @ 2025-04-12 17:57  N3ptune  阅读(364)  评论(0)    收藏  举报