1. Makefile编译带头文件的程序
本文是上一篇博客的延续,其中没有对基础知识进行详细描述,若需要查看,请到学习笔记-Makefile基础及入门1中查看。
提要:Makefile文件的执行采用递归的模式,他会根据“目标和依赖关系”递归地执行命令。
1.1 示例
本例与学习笔记-Makefile基础及入门1的第3部分简单示例相比增加了头文件,更贴近与实际的项目。增加了头文件后编译指令需要增加头文件目录的索引,以确保编译器能准确地找到需要的头文件。
#实验项目文件结构
PS E:\8.test_demo\1.makefileTest> ls
目录: E:\8.test_demo\1.makefileTest
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2024/3/14 21:35 .vscode
d----- 2024/3/17 9:40 bin
d----- 2024/3/16 20:56 include
d----- 2024/3/17 9:40 obj
d----- 2024/3/14 21:42 src
-a---- 2024/3/17 9:39 447 Makefile
PS E:\8.test_demo\1.makefileTest\src> ls
目录: E:\8.test_demo\1.makefileTest\src
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2024/3/16 21:08 60 func1.c
-a---- 2024/3/16 21:08 61 func2.c
-a---- 2024/3/16 21:11 139 main.c
PS E:\8.test_demo\1.makefileTest\include> ls
目录: E:\8.test_demo\1.makefileTest\include
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2024/3/16 21:09 35 func1.hpp
-a---- 2024/3/16 21:09 35 func2.hpp
如果这个test_demo仍采用上一篇博客最后一个示例的Makefile文件进行编译将出现以下报错:
#编译器找不到头文件
src/func2.c:1:10: fatal error: func2.hpp: No such file or directory
#include "func2.hpp"
^~~~~~~~~~~
compilation terminated.
以下是优化过的Makefile文件,make它可实现正常的编译运行
cpp_srcs := $(wildcard src/*.c)
cpp_objs := $(patsubst src/%.c,obj/%.o,$(cpp_srcs))
include_path := E:\8.test_demo\1.makefileTest\include \ #头文件路径
I_flags := $(include_path:%=-I%) #-I头文件目录,用于添加到编译选项中
compile_opt := -g -O3 -w $(I_flags) #编译选项
obj/%.o : src/%.c
@g++ -c $^ -o $@ $(compile_opt) #.o文件编译中增加了编译选项
bin/exec : $(cpp_objs)
@g++ $^ -o $@
compile : bin/exec
run : bin/exec
@$^
debug :
@echo $(I_flags)
clean :
@del /q bin obj
.PHONY : compile clean
1.2 Makefile指定头文件的步骤
- 获得头文件路径
一般直接在编辑器文件夹列表中找到对应的文件夹,右击选择复制路径即可;
include_path := E:\8.test_demo\1.makefileTest\include \
# \为续行符,一般用于在之后换行添加头文件路径
- 在头文件所在目录前增加-I选项
-I选项用于在编译指令中指定-I后面的目录为头文件所,编译器会根据这个目录去寻找对应的头文件;
I_flags := $(include_path:%=-I%)
#该条语句表示将include_path中的每一项文件目录之前都增加“-I”标志,并将新的字符串赋值给I_Flag变量
- 将-I头文件目录加到编译指令后
obj/%.o : src/%.c #通配符“%”表示obj/和src/目录下的每个文件,在本例中表示main.o : main.c, func1.o : func1.c, func2.o : func2.c
@g++ -c $^ -o $@ $(compile_opt) #再次提醒$@为依赖关系中“:”左边的所有项目在本例中指“obj/%.o”;$^为“:”右边的所有项目,在本例中指“obj/%.c”,另外$<表示“:”右边的第一项
#编译选项放在编译指令目标文件之后
1.2 常用编译选项
1.2.1 优化选项
-O(大写):第一级别优化,编译时对源代码进行基本优化(主要为:跳转和延迟退栈),有助于程序运行更快;
-O2:第二级别优化,在O1的基础上尽可能优化程序空间(减小程序的大小)和优化运行效率;
-O3:第三级别优化,在O2的基础上增加了inline(内联函数)和寄存器优化;
-Os:优化级别类似于O2,常用与生成最终版本
1.2.2 出错提示
-w:忽略所有警告;
-Wall:开启大部分警告提示;
1.2.3 指定目录和库
-I:大写的“i”,指定头文件目录,绝对路径和相对路径均可用。/usr/include目录一般不用指定;
-L:指定库文件目录,绝对路径和相对路径均可用;
-l:小写的“L”,指定程序要链接的库,后面接库名(库文件名去除lib和后缀);
1.2.4 其它常用选项
-g:在生成的可执行文件中包含标准调试信息;
-std=:语言标准。
2. Makefile编译静态库编译与链接
2.1 示例
该示例使用的实验源码与1.1示例相同。实验过程分为两个部分:静态库编译和静态库链接。其中没有对基础知识进行详细描述,若需要查看,请到学习笔记-Makefile基础及入门1中查看。
2.1.1 静态库编译
lib_srcs := $(filter-out src/main.c,$(wildcard src/*.c)) #筛选出源文件。该处使用了函数嵌套,建议由内往外以$(...)为单元进行阅读
lib_objs := $(patsubst src/%.c,obj/%.o,$(lib_srcs)) #.o目标文件名生成
include_path := include \ #使用的相对路径
I_flags := $(include_path:%=-I%)
compile_opt := -g -O3 -w $(I_flags)
obj/%.o : src/%.c #首先将源文件编译成.o文件
@g++ -c $^ -o $@ $(compile_opt)
lib/libfunc.a : $(lib_objs) #编库,Windows平台下GNU编译器生成的静态库后缀为.a
@ar -r $@ $^
staticLib : lib/libfunc.a
debug :
@echo $(lib_objs)
clean :
@del /q lib obj
.PHONY : staticLib clean
整个过程包括3步:
- 确认源文件;
- .o文件编译(编辑.o文件与源文件依赖关系,.o文件编译指令,注意.o文件编译选项);
- .a(静态库文件)编译(编辑.a文件与.o文件依赖关系,.a文件编译指令);
ar -r [libxxx.a] [.o] [.o] [.o]...
2.1.2 静态库链接
include_path := include \
#源文件编译成.o文件
I_flags := $(include_path:%=-I%)
compile_opt := -g -O3 -w $(I_flags)
obj/main.o : src/main.c
@g++ -c $^ -o $@ $(compile_opt)
#链接库的-*l*和-L选项
lib_path := E:\8.test_demo\1.makefileTest\lib
linking_libs := $(patsubst lib/lib%.a, % ,$(wildcard lib/*.a))
l_options := $(linking_libs:%=-l%)
L_options := $(lib_path:%=-L%)
linking_flags := $(l_options) $(L_options)
#可执行文件的依赖项和生成指令
bin/exec : obj/main.o
g++ $^ -o $@ $(linking_flags)
#伪目标run:运行可执行文件
run : bin/exec
@$^
debug :
@echo $(lib_srcs)
@echo $(lib_objs)
clean :
@del /q obj bin
.PHONY : staticLib clean run
静态库链接过程包括3步:
- 源文件编译生成.o文件(注意-I(大写“i”,头文件)选项。特别注意:依赖于库时,需要将库的相关头文件添加到-I选项);
- 库路径编译选项生成(-l(小写“L”)选项,-L选项);
-l[库名称:libxxx.a中的xxx]
-L[库所在目录的路径]
- 可执行文件编译(链接.o文件与.a文件);
g++ [.o文件] [.o文件]... -o [可执行文件] -l[库名] -L[库路径]
2.2实验过程中出现的问题
- 变量的使用放在初始化之前导致错误;
- 链接静态库时,静态库中的头文件路径未指定,导致无法找到相应文件而导致编译失败;
- 字符串变量赋值的最后一项后不要留有“/”续行符,易导致make异常。
3. Makefile编译动态库编译与链接
3.1 示例
该示例使用的实验源码与1.1示例相同。实验过程分为两个部分:静态库编译和静态库链接。其中没有对基础知识进行详细描述,若需要查看,请到学习笔记-Makefile基础及入门1中查看。
3.1.1 动态库编译
实验过程中发现在Windows平台上用以下Makefile代码(可用于Linux平台),编译生成的动态库无法进行链接,根据bilibili用户“algoXman”在李呵欠UP主视频下的留言确认,是因为Windows平台下无法指定动态链接库的运行时目录(rpath不可用)。
lib_srcs := $(filter-out src/main.c,$(wildcard src/*.c)) #筛选出源文件。该处使用了函数嵌套,建议由内往外以$(...)为单元进行阅读
lib_objs := $(patsubst src/%.c,obj/%.o,$(lib_srcs)) #.o目标文件名生成
include_path := E:\8.test_demo\1.makefileTest\include
I_flags := $(include_path:%=-I%)
compile_opt := -g -O3 -w -fPIC $(I_flags)
obj/%.o : src/%.c #首先将源文件编译成.o文件
@g++ -c $^ -o $@ $(compile_opt)
lib/libfunc.so : $(lib_objs) #编库
@g++ -shared $^ -o $@
dynamicLib : lib/libfunc.so
debug :
@echo $(I_flags)
clean :
@del /q lib obj
.PHONY : dynamicLib clean
解决办法为动态库放到和可执行文件同一个目录下。
#将编库部分更改为:
bin/libfunc.dll : $(lib_objs) #编库,将动态库文件放在可执行文件相同的目录下(bin/),Windows平台下动态库后缀为.dll
@g++ -shared $^ -o $@
dynamicLib : bin/libfunc.dll
整个过程包括3步:
- 确认源文件;
- .o文件编译(与静态库相比,编译选项中需要增加“-fPIC”选项);
- .so(动态库文件)编译;
g++ -shared [.o] [.o]... -o [lib库名.dll]
3.1.2 动态库链接
#Linux平台
include_path := E:\8.test_demo\1.makefileTest\include
#源文件编译成.o文件
I_flags := $(include_path:%=-I%)
compile_opt := -g -O3 -w $(I_flags)
obj/main.o : src/main.c
@g++ -c $^ -o $@ $(compile_opt)
#链接库的-*l*和-L选项
lib_path := E:\8.test_demo\1.makefileTest\lib
linking_libs := $(patsubst lib/lib%.so,%,$(wildcard lib/*.so))
l_options := $(linking_libs:%=-l%)
L_options := $(lib_path:%=-L%)
r_options := $(lib_path:%=-Wl,-rpath=%)
linking_flags := $(l_options) $(L_options) $(r_options)
#可执行文件的依赖项和生成指令
bin/exec : obj/main.o
@g++ $^ -o $@ $(linking_flags)
#伪目标run:运行可执行文件
run : bin/exec
@$^
debug :
@echo $(linking_flags)
clean :
@del /q obj bin
.PHONY : clean run
#将上面的Makefile文件直接在Windows平台运行会产生如下报错
C:/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lfunc
collect2.exe: error: ld returned 1 exit status
mingw32-make: *** [Makefile:18: bin/exec] Error 1
针对Window平台的动态库链接Makefile文件如下:
#Windows平台
include_path := E:\8.test_demo\1.makefileTest\include
#源文件编译成.o文件
I_flags := $(include_path:%=-I%)
compile_opt := -g -O3 -w $(I_flags)
obj/main.o : src/main.c
@g++ -c $^ -o $@ $(compile_opt)
#链接库的-*l*和-L选项
lib_path := E:\8.test_demo\1.makefileTest\bin
linking_libs := $(patsubst bin/lib%.dll,%,$(wildcard bin/*.dll))
l_options := $(linking_libs:%=-l%)
L_options := $(lib_path:%=-L%)
linking_flags := $(l_options) $(L_options) #Windows平台与Linux平台相比,无-Wl,rpath[]选项
#可执行文件的依赖项和生成指令
bin/exec : obj/main.o
@g++ $^ -o $@ $(linking_flags)
#伪目标run:运行可执行文件
run : bin/exec
@$^
debug :
@echo $(I_flags)
clean :
@del /q obj bin
.PHONY : clean run
动态库链接过程包括3步:
- 源文件编译生成.o文件(注意-I(大写“i”,头文件)选项。特别注意:依赖于库时,需要将库的相关头文件添加到-I选项);
- 库路径编译选项生成(-l(小写“L”)选项,-L选项);
-l[库名称:libxxx.dll中的xxx]
-L[库所在目录的路径]
- 可执行文件编译(链接.o文件与.dll文件);
g++ [.o文件] [.o文件]... -o [可执行文件] -l[库名] -L[库路径]
3.2实验过程中出现的问题
- 编Windows平台译动态库到与可执行文件不同的目录下,导致无法链接到库;
- 库路径输入错误导致无法找到库中间的函数;