学习笔记——Makefile基础及入门2

本文详细介绍了如何在Makefile中处理头文件,以及如何编译和链接静态库与动态库,包括优化选项、库路径设置和跨平台注意事项。
摘要由CSDN通过智能技术生成

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指定头文件的步骤

  1. 获得头文件路径
    一般直接在编辑器文件夹列表中找到对应的文件夹,右击选择复制路径即可;
include_path := E:\8.test_demo\1.makefileTest\include	\
# \为续行符,一般用于在之后换行添加头文件路径
  1. 在头文件所在目录前增加-I选项
    -I选项用于在编译指令中指定-I后面的目录为头文件所,编译器会根据这个目录去寻找对应的头文件;
I_flags := $(include_path:%=-I%)
#该条语句表示将include_path中的每一项文件目录之前都增加“-I”标志,并将新的字符串赋值给I_Flag变量
  1. 将-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步:

  1. 确认源文件;
  2. .o文件编译(编辑.o文件与源文件依赖关系,.o文件编译指令,注意.o文件编译选项);
  3. .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步:

  1. 源文件编译生成.o文件(注意-I(大写“i”,头文件)选项。特别注意:依赖于库时,需要将库的相关头文件添加到-I选项);
  2. 库路径编译选项生成(-l(小写“L”)选项,-L选项);
	-l[库名称:libxxx.a中的xxx]
	-L[库所在目录的路径]
  1. 可执行文件编译(链接.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步:

  1. 确认源文件;
  2. .o文件编译(与静态库相比,编译选项中需要增加“-fPIC”选项);
  3. .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步:

  1. 源文件编译生成.o文件(注意-I(大写“i”,头文件)选项。特别注意:依赖于库时,需要将库的相关头文件添加到-I选项);
  2. 库路径编译选项生成(-l(小写“L”)选项,-L选项);
	-l[库名称:libxxx.dll中的xxx]
	-L[库所在目录的路径]
  1. 可执行文件编译(链接.o文件与.dll文件);
g++ [.o文件] [.o文件]... -o [可执行文件] -l[库名] -L[库路径]

3.2实验过程中出现的问题

  • 编Windows平台译动态库到与可执行文件不同的目录下,导致无法链接到库;
  • 库路径输入错误导致无法找到库中间的函数;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZW_finder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值