保姆级讲解:对一个项目使用Makefile生成静态库并带你看懂Makefile文件内容
到这步基本上没有啥大问题了,这一步用于实现某些功能,我这里简单地列举了两个。clean(名字也是自己定),之后在终端make+自己定义的名字,就会实现定义名字对应的功能。比如说在终端输入“make clean”,就会对Build目录,Executable目录和Library目录的删除,用于重新执行make命令clean:#clean实现清除指定的文件夹run:#run实现编译Executable文
1.静态库的概念与优势
- 静态库的定义:编译时直接链接到目标程序,运行时无需外部依赖。
- 生成静态库的好处:
- 提升代码复用性,避免重复编译相同代码。
- 简化项目管理,通过库文件隔离模块实现分块开发。
- 增强程序独立性,无需担心动态库版本兼容性问题。
2.Makefile生成静态库的核心逻辑
- 自动化编译流程:通过规则定义
.o
文件生成与库打包步骤。 - 依赖关系管理:自动处理头文件变更,触发必要的重新编译。
- 增量构建优化:仅重新编译修改过的文件,节省时间。
3.Makefile内容逐行解析(以本人的一个小项目为例)
Makefile内容大概预览:
项目目录如下(项目内容不重要,博主太水了,是个乐色小项目),Source存放的是项目的.c文件,Include存放的是头文件。其他的与本文章关系不大,就不一一介绍了。
因为用 Makefile 管理项目时,核心思想是 “分离编译、按需链接”。
所以注意!一般不要将main.c文件也进行编译生成库,因为库是跟别人使用的,别人只是使用里面的功能模块,一般有自己的主文件,所以编译的时候记得把main.c文件放出要编译的文件夹中。
.o文件是博主生成过一次才有的,大家没有也不要慌张,后面运行Makefile文件后就会生成了。
之后,就可以创建自己的Makefile文件了。(Makefile文件的命名只能是“Makefile”,大小写也要严格区分)
OK啊,本文章的重中之重来了,就是如何看懂Makefile文件?大家刚开始看是一头雾水,没关系,接下来我将保姆级注释带大家看懂。(注释有点暗,辛苦大家仔细看看了)
Makefile文件讲解!
还有还有一个提前说明,大家自己写Makefile文件一定一定要看清楚那些使用到缩进(也就是tab键)的地方,Makefile文件对tab很敏感,你tab不能用空格替代或者tab和空格混合!
比如我这里rm前面是用空格替代的
就出现以下错误,把多余的空格或者不符合规则的多余的tab删掉就行了
1.初始化配置阶段:
这段代码是 Makefile 的 “初始化配置阶段”,核心作用是定义编译所需的 “基础变量”—— 把编译器、文件路径、编译规则等关键信息用变量存储起来,方便后续规则(编译、链接、生成静态库)统一调用,避免重复写代码。
说个注意点,一定一定要看清楚自己的.c文件和.h文件的路径,有些小伙伴为了方便看或者仔细一点会把.c文件和.h文件细分成几个小目录
CC = gcc
# 定义编译器为gcc,后续用$(CC)引用
ELF = ./Executable/main
# 定义最终生成的可执行文件路径和名称
SRCS = $(wildcard ./Source/*.c)
# 获取Source目录下所有.c源文件列表(不包含main.c,避免其被编译进静态库)
OBJS = $(patsubst ./Source/%.c, ./Build/%.o, $(SRCS))
# 将源文件路径转换为目标文件路径,如./Source/a.c → ./Build/a.o
#就是把所有的.c替换成.o,然后在哪读取呢?就是在SRCS中取,在文件夹Source中获取
#全部.c文件
CFLAGS = -I ./Include -L ./Library -lpthread -Wall -g -O1
# 编译选项说明:
# -I ./Include:指定头文件搜索路径为当前目录下的Include文件夹
# -L ./Library:指定库文件搜索路径为当前目录下的Library文件夹
# -lpthread:链接pthread线程库
# -Wall:开启所有警告信息,提高代码质量
# -g:生成调试信息,用于GDB调试
# -O1:开启一级优化,平衡编译速度和执行效率
这里说一下,-Wall,-g,-o1这三个可以注释掉,特别是-Wall,说是提高代码质量,实际上开启之后你原本可以运行的代码会报一万个警告,看着十分不顺眼。比如说你的printf函数是有返回值的,但是你没用上这个返回值,他就会警告你要用上(纯折磨自己,只要代码本身没有警告影响运行结果就行了)
2.设置静态库生成路径
静态库都是.a为后缀,这里的Library是存放库文件的文件夹,名字按照自己的爱好命名即可
STATIC_LIB = ./Library/libims.a
#设置静态库文件的生成路径
3.核心构建规则定义阶段
#先说明一个符号概念:符号":"是规则定义的核心符号,用来分隔「目标」和「它所依赖的文件 / 目标」
all:$(ELF)
#定义目标all,它依赖于ELF,即main文件
$(ELF):$(OBJS) ./main.c $(STATIC_LIB)
#可执行的文件依赖:所有目标文件、main.c和静态库
#也就是main依赖于所有Source目录下.c文件生成的.o文件、main.c文件以及静态库
#同样的说一个符号概念"@":隐藏命令本身的输出,只显示命令的执行结果(如果有的话)
@mkdir -p $(@D)
#mkdir -p 是创建目录的命令,-p是递归生成,$(@D) 表示目标文件所在的目录,D是directory的缩写,也就是目录
#这条命令的作用:确保编译生成的 .o 文件有地方放—— 不管 Build 目录是否存在,都会提前准备好目录
$(CC) $(OBJS) ./main.c $(STATIC_LIB) -o $(ELF) $(CFLAGS)
#相当于用gcc编译:所有Source目录下.c文件生成的.o文件、main.c文件和静态库链接成可执行文件
4.静态库构建阶段
#静态库构建规则
$(STATIC_LIB):$(OBJS)
#依然定义目标all,它依赖于所有Source目录下.c文件生成的.o文件
@mkdir -p $(@D)
#mkdir -p 是创建目录的命令,-p是递归生成,$(@D) 表示目标文件所在的目录
ar rcs $@ $(OBJS)
# ar是静态库管理工具
# r:替换或添加文件到库中
# c:创建库(如果不存在)
# s:为库创建索引(加速链接)
# $@表示目标(静态库),$(OBJS)表示所有依赖的目标文件
5.目标文件编译阶段
#目标文件编译规则
./Build/%.o:./Source/%.c
#这里就是Build目录下的.o文件都依赖于Source下的.c文件
#依赖符号":"左边是目标文件模板,"%"代表任意字符串,最终都会生成./Build/ .o文件
#右边是依赖文件模板,% 和左边完全对应 —— 左边是 ./Build/a.o,右边就对应 ./Source/a.c
@mkdir -p $(@D)
#mkdir -p 是创建目录的命令,-p是递归生成,$(@D) 表示目标文件所在的目录
$(CC) -c $< -o $@ $(CFLAGS)
#把 .c 文件编译成 .o 文件,$<:Makefile 自动变量,代表当前规则的 “第一个依赖文件”(也就是右边的 ./Source/%.c,比如 ./Source/a.c)
#-o 是 “指定输出文件” 的选项,$@ 代表当前规则的 “目标文件”(比如 ./Build/a.o),合起来就是 “把编译结果输出为 ./Build/a.o”
#最后调用之前定义的编译选项CFLAGS
#其实就是比如说
#./Build/dlink_cir_list.o:./Source/dlink_cir_list.c
#这两个相匹配,这条命令会展开成:
#gcc -c ./Source/dlink_cir_list.c -o ./Build/dlink_cir_list.o -I ./Include -L ./Library -lpthread -Wall -g -O1
6.伪目标定义
到这步基本上没有啥大问题了,这一步用于实现某些功能,我这里简单地列举了两个。
clean(名字也是自己定),之后在终端 make+自己定义的名字,就会实现定义名字对应的功能。
比如说在终端输入“make clean”,就会对Build目录,Executable目录和Library目录的删除,用于重新执行make命令
.PHONY:clean
clean:
rm -rf ./Build ./Executable ./Library
#clean实现清除指定的文件夹
run:
./Executable/main
#run实现编译Executable文件夹下的main可执行文件
总结
- 静态库适合需移植性强、依赖简单的场景。
- Makefile通过规则定义实现高效构建,需熟练掌握变量与模式匹配的用法。
试着对自己的项目用Makefile进行封装吧,生成库之后就能给别人使用了,别人也看不到你的函数实现。
对Makefile的内容讲解就到此结束了,有什么不懂的都可以在评论区提问,有时间我会予以解答。
更多推荐
所有评论(0)