文档声明:
以下资料均属于本人在学习过程中产出的学习笔记,如果错误或者遗漏之处,请多多指正。并且该文档在后期会随着学习的深入不断补充完善。感谢各位的参考查看。笔记资料仅供学习交流使用,转载请标明出处,谢谢配合。
如果存在相关知识点的遗漏,可以在评论区留言,看到后将在第一时间更新。
作者:Aliven888
文章目录
1、简述
在 linux 系统中 make 是一个非常重要的编译命令,不管是自己进行项目开发还是安装应用软件,我们都经常要用到 make 或 makeinstall 。利用 make 工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也可以执行操作系统的命令。makefile 带来的好处就是“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大
这里是引用
的提高了软件开发的效率。
make 命令是GNU的工程化编译工具,用于编译众多相互关联的源代码问价,以实现工程化的管理,提高开发效率。利用 make 工具可以自动完成编译工作,这些工作包括:
- 如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
- 如果修改了某几个源文件,则只重新编译这几个源文件。
- 如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。
利用这种自动编译可以大大简化开发工作,避免不必要的重新编译。make 工具通过一个称为 Makefile 的文件来完成并自动维护编译工作,Makefile 文件描述了整个工程的编译、连接规则。
Make的工作原理:
当 make 命令被执行时,它会扫描当前目录下 Makefile 或 makefile 文件找到目标以及其依赖。如果这些依赖自身也是目标,继续为这些依赖扫描Makefile 建立其依赖关系,然后编译它们。一旦主依赖编译之后,然后就编译主目标,假设你对某个源文件进行了修改,你再次执行 make 命令,它将只编译与该源文件相关的目标文件,因此,编译完最终的可执行文件节省了大量的时间。
2、工具安装
make工具依赖 gcc ,automake,autoconf,libtool,安装 make 工具这些安装包需要一起安装。
运行如下命令来完成安装:
yum install gcc automake autoconf libtool make
安装完成后,通过查看一下版本信息,确认是否安装成功。
3、make 指令介绍
格式:
make + [option] + [file]
option:
| 指令 | 内容 |
|---|---|
| -f | 指定‘Makefile’文件; |
| -i | 忽略命令执行返回的出错信息; |
| -s | 沉默模式,在执行之前不输出相应的命令行信息; |
| -r | 禁止使用build-in规则; |
| -n | 非执行模式,输出所有执行命令,但并不执行; |
| -t | 更新目标文件; |
| -q | make操作将根据目标文件是否已经更新返回‘0’或非‘0’的状态信息; |
| -p | 输出所有宏定义和目标文件描述; |
| -d | Debug模式。输出有关文件和检测时间的详细信息。 |
| -c dir | 在读取makefile之前改变到指定的目录dir; |
| -I dir | 当包含其他Makefile文件时,利用该选项指定搜索目录; |
| -h | help文档,显示所有的make选项; |
| -w | 在处理makefile之前和之后,都显示工作目录。 |
| -k | 作用是在让make命令在发现错误时仍然就执行,而不是在检测到第一个错误时就停止,所以可是使用这个选项在一次操作中发下所有未编译成功的源文件 |
file:制定编译目标文件
4、Makefile 语法介绍
4.1、makefile的命名(两种)
- makefile
- Makefile (通常建议使用这种)
4.2、 makefile的书写规则
- 规则的三个要素:(生成)目标、依赖(关系)、命令
在 Makefile 中,规则的顺序是很重要的,因为, Makefile 中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让 make 知道你的最终目标是什么。一般来说,定义在 Makefile 中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make 所完成的也就是这个目标。
a.o : a.cpp commDefine.h 表示 a.o 的生成 依赖于 a.cpp 和 commDefine.h 。
格式:
# target 也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。
# prerequisites 就是,要生成那个target所需要的文件或是目标(简称:依赖文件)。
# command 也就是make需要执行的命令。(任意的Shell命令)
# 在看别人写的Makefile文件时,你可能会碰到以下三个变量:$@,$^,$<代表的意义分别是:
# $@--目标文件, $^--所有的依赖文件, $<--第一个依赖文件
# ************************ 编译规则 ****************************
target:prerequisites ... # 如果一行显示不下 可以使用 \ (换行符), 在下一行继续书写
command # 注意,这里前面的 tab键 不能缺少
# 或者这样写
target:prerequisites ... ; command #写在同一行的话,需要使用 分号(";")
# .PHONY意思表示 XXXX 是一个“伪目标”
.PHONY : XXXX
XXXX: # 自定义动作,可以直接通过make指令执行,比如 clean
xxxxxxxx # 自动动作的执行 sh 命令
# ************************ 变量定义 ****************************
# 定义变量的好处就是,如果同一组 [*.o] 被多处调用时,我们需要将每一出都进行修改,
# 但是如果该组 [.o] 定义成变量, 那么我们每次变更时,只需要修改一处即可了。
# 定义
valueName = value
# 变量是可以相加的
value += value1
# 变量的赋值
value := value2
# 使用:
$(valueName) 或者 ${valueName}
4.3、多文件的makefile的编写
makefile 可以有多个规则,当第一个规则的命令在执行的时候发现没有相应的依赖,就在下面的规则中找。最上面的规则的目标是终极目标一定写在最上面,也就是最后要生成的文件。
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
- 显式规则:显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
- 隐晦规则:由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
- 变量的定义:在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
- 文件指示:其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
- 注释:Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。
# ************************ 常见写法 ****************************
# 用户编写链接关系
# 以下面的情况为例:在找不到 a.o、b.o 中的某一个或者多个时,
# 往下执行 a.cpp、b.cpp 的编译过程,去生成 file 文件,然后在供第一行中的去使用。
main:../Public/a.o ../Public/b.o
g++ -o main main.cpp ../Public/a.o ../Public/b.o
a.o:a.cpp commDefine.h
g++ -c ../Public/a.cpp
b.o:b.cpp commDefine.h
g++ -c ../Public/b.cpp
.PHONY : clean
clean:
@echo '123456'
-rm ../Public/*.o main #在 rm 命令前面加'-',表示也许某些文件出现问题,但不要管,继续做后面的事。
# ************************ make 自动推导 ************************
# GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,
# 于是我们就没必要去在每一个[.o]文件后都写上类似的命令,
# 因为,我们的make会自动识别,并自己推导命令。
# 这种方法,也就是make的“隐晦规则”。
# 只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,
# a.o,那么a.cpp,就会是a.o的依赖文件。
# 并且 cc -c a.cpp 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。
# 定义变量
pubPath=../Public
Obj=$(pubPath)/a.o $(pubPath)/b.o
main : $(Obj)
g++ -o main main.cpp $(Obj)
a.o : $(pubPath)/a.cpp commDefine.h
b.o : $(pubPath)/b.cpp commDefine.h
.PHONY : clean
clean:
@echo 'delete all [.o] files.'
-rm $(Obj) main
# **************** Makefile 中调用其他的 Makefile ***************
# 在Makefile使用include关键字可以把别的Makefile包含进来,
# 这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置
# 如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:
# -include<filename>
# 其表示,无论include过程中出现什么错误,都不要报错继续执行。
# 格式:
-include filename # filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)
# 举例:
-include Makefile0 Makefile1 Makefile2 ..
4.4、通配符在Makefile中的使用
在 Makefile 中支持的通配符有以下几种:
- ~表示当前用户的$HOME目录
- *表示一系列文件,比如:*.cpp
~/Download : 表示当前用户的 $HOME 目录下的 Download 目录
rm -f *.o : 表示删除当前目录下,后缀是 .o 的全部文件
4.5、文件查找
在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当 make 需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉 make,让 make 在自动去找。
Makefile 文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make 就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src:…/headers
上面的的定义指定两个目录,“src”和“…/headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:
- vpath < pattern> < directories>为符合模式< pattern>的文件指定搜索目录。
- vpath < pattern>清除符合模式< pattern>的文件的搜索目录。
- vpath清除所有已被设置好了的文件搜索目录。
vapth使用方法中的< pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。< pattern>指定了要搜索的文件集,而< directories>则指定了的文件集的搜索的目录。例如:
vpath %.h …/headers
该语句表示,要求make在“…/headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)
我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的< pattern>,或是被重复了的< pattern>,那么,make会按照vpath语句的先后顺序来执行搜索。如:
- vpath %.c foo
- vpath % blish
- vpath %.c bar
其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。
- vpath %.c foo:bar
- vpath % blish
而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。
4.6、打印输出(执行过程)
通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被 make 显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:
@echo 正在编译XXX模块…
当 make 执行时,会输出“正在编译XXX模块…”字串,但不会输出命令,如果没有“@”,那么,make将输出:
- echo 正在编译XXX模块…
- 正在编译XXX模块…
如果 make 执行时,带入make参数“-n”或“–just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的 Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。
而 make 参数 “-s” 或 “–slient” 则是全面禁止命令的显示。
4.7、命令执行
当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号(";")分隔这两条命令。
# 比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行。下面两种写法,你觉得那种比较合理?
# 示例一:
exec:
cd /home/Aliven
pwd
# 示例二:
exec:
cd /home/Aliven; pwd
# 实际演示后,我们发现,当我们执行“make exec”时,
# 第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,
# 第二个例子中,cd就起作用了,pwd会打印出“/home/Aliven”。
5. 静态库 / 动态库

Linux静态库命名规则:
Linux静态库命名规范,必须是"lib[your_library_name].a";lib为前缀,中间是静态库名,扩展名为.a 。
linux动态库的命名规则:
Linux动态链接库的名字形式为 libxxx.so,必须是"lib[your_library_name].so";前缀是lib,后缀名为".so" 。
引用规则:
动态库的引用规则与静态库的一样,如下:
# Demo 调用了 libxxx1.a libxxx2.so 两个库
# 库的头文件放在 ../include 目录下
# 库文件放在 ../lib 目录下
# Makefile 书写规则
Demo:hikDemo.cpp
g++ -g -o Demo Demo.cpp -I../include -L../lib -lxxx1 -lxxx2
- -g 可执行文件中嵌入调试信息。
- -o 输出文件名
- Demo.cpp 输入文件(编译目标文件)
- -Wl,-rpath=/dd/dd:提示链接器,在可执行文件中添加库搜索路径。
- -I :(大写字母 i )添加头文件搜索路径(无论动态库还是静态库,都需要指定头文件)。
- -L:(大写字母 l)表示要连接的库所在目录(声明共享库或静态库搜索路径)
- -l:(小写字母 l)指定链接时需要的动(静)态库,编译器查找动(静)态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库的名称。因此只需要写 库 名称即可。
静态库特点总结:
静态库对函数库的链接是放在编译时期完成的。
程序在运行时与函数库再无瓜葛,移植方便。
浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
动态库特点总结:
动态库把对一些库函数的链接载入推迟到程序运行的时期。
可以实现进程之间的资源共享。(因此动态库也称为共享库)
将一些程序升级变得简单。
甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
在执行的时候是如何定位共享库文件的呢?
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。
对于 elf 格式的可执行程序,是由 ld-linux.so* 来完成的,它先后搜索 elf 文件的 DT_RPATH 段—环境变量 LD_LIBRARY_PATH—/etc/ld.so.cache 文件列表— /lib/,/usr/lib 目录找到库文件后将其载入内存。
如何让系统能够找到它:
如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。
如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下:
– 编辑 /etc/ld.so.conf 文件,加入库文件所在目录的路径
– 运行 ldconfig ,该命令会重建 /etc/ld.so.cache 文件
我们将创建的动态库复制到 /usr/lib下面,然后就可以运行程序了。
通配符
* : 表示任意字符,例如,“*.h”表示所有以“.h”结尾的文件。
% :匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。
$< :一个个匹配文件
$@ :表示目标文件
$^ :所有的依赖文件