在之前我们已经学习了一个文件的编译过程,但是做过项目的都知道,一个工程中的源文件不计其数,其按类型、功能、模块会分别放在若干个目录中,而这些文件如何编译就需要有一个编译规则,虽然现在很多大型的项目都是直接用诸如CMake、Autotools、SCons等工具生成的,但是对于一些小项目,使用工具就没有什么必要了,而且对于项目编写来说,掌握Makefile的基本知识对我们来说也是很有必要的。

  我们本篇文章所要讲解的就是这个编译规则的文件makefile的规则及如何进行编写。makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

1.0从一个简单的例子开始

  这里,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,在这个例子中,我们会看到一份主程序代码(main.c)、三份函数代码(getop.c、stack.c、getch.c)以及一个头文件(calc.h)。通常情况下,我们需要这样编译它:

gcc -o calc main.c getch.c getop.c stack.c 

如果没有makefile,在开发+调试程序的过程中,我们就需要不断地重复输入上面这条编译命令,要不就是通过终端的历史功能不停地按上下键来寻找最近执行过的命令。这样做两个缺陷:

  1. 一旦终端历史记录被丢失,我们就不得不从头开始;

  2. 任何时候只要我们修改了其中一个文件,上述编译命令就会重新编译所有的文件,当文件足够多时这样的编译会非常耗时。

为了减少麻烦和节约时间,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:

    1.如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。

2.如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。

3.如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。

1.1 Makefile的规则

target ... : prerequisites ...
command
...
...

target也就是一个目标文件,可以是Object File,也可以是执行文件。

prerequisites就是,要生成那个target所需要的文件或是目标。

command也就是make需要执行的命令。(任意的Shell命令)

  这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

通过了解规则,我们就掌握了makefile的核心内容,但是仅仅靠这个是无法自己编写好一个完美的makefile文件的。

这里我们看回到我们之前的示例,按照这个规则我们可以写出如下的一个简单的makefile文件:

   calc: main.c getch.c getop.c stack.c
gcc -o calc main.c getch.c getop.c stack.c

我么可以看到它主要分成了三个部分,第一行冒号之前的calc,就是我们所要编译的这个程序calc。冒号后面的部分(main.c getch.c getop.c stack.c),就是我们称之为依赖关系表,也就是编译calc所需要的文件,这些文件只要有一个发生了变化,就会触发该语句的第三部分,我们称其为命令部分。

这里值得注意的是:请注意,在第二行的“gcc”命令之前必须要有一个tab缩进。语法规定Makefile中的任何命令之前都必须要有一个tab缩进,否则make就会报错。

这样,一个可以进行编译的makefile文件写出来了,但是,我们可以看出,整个makefile文件比较基础,没有进行过优化,这样,在编译比较大且文件多的工程的时候效率是远远达不到的,所以我们需要通过下面的学习来进行重构,以提高整个makefile的编译效率。

1.3makefile中使用变量

有时候我们会需要在makefile中增添文件,我们这个makefile显然可以直接在依赖链和指令中直接添加文件名来完成,但是当文件较大时想要直接添加可能就比较复杂和耗费时间了,所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。

    cc = gcc
prom = calc
src = main.c getch.c getop.c stack.c $(prom): $(src)
$(cc) -o $(prom) $(src)
如你所见,我们在上述代码中定义了三个常量cc、prom以及src。它们分别告诉了make我们要使用的编译器、要编译的目标以及源文件。这样一来,今后我们要修改这三者中的任何一项,只需要修改常量的定义即可,而不用再去管后面的代码部分了。

1.4、make是如何工作的

  之前进行过优化后,我们解决了增添文件的问题,但我们现在依然还是没能解决当我们只修改一个文件时就要全部重新编译的问题。而且如果我们修改的是calc.h文件,make就无法察觉到变化了(所以有必要为头文件专门设置一个常量,并将其加入到依赖关系表中)。下面,我们需要通过了解make是如何进行工作的,来对整个makefile文件进行优化。在默认的方式下,也就是我们只输入make命令。那么,

1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。当名字不为这俩着之一时,可以用以下方法指定。

make f 文件名

2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“calc”这个文件,并把这个文件作为最终的目标文件。
3、如果calc文件不存在,或是calc所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
4、如果calc所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件calc了。

通过了解make是如何工作的,我们知道可以在标准的编译过程中,源文件往往是先被编译成目标文件,然后再由目标文件连接成可执行文件的。我们可以利用这一点来调整一下这些文件之间的依赖关系:
    cc = gcc
prom = calc
deps = calc.h
obj = main.o getch.o getop.o stack.o
$(prom): $(obj)
$(cc) -o $(prom) $(obj) main.o: main.c $(deps)
$(cc) -c main.c getch.o: getch.c $(deps)
$(cc) -c getch.c getop.o: getop.c $(deps)
$(cc) -c getop.c stack.o: stack.c $(deps)
$(cc) -c stack.c

这样一来,上面的问题显然是解决了,但是现在makefile文件又变的啰嗦了,这里还是可以用变量的方法来对其进行简化

    cc = gcc
prom = calc
deps = calc.h
obj = main.o getch.o getop.o stack.o $(prom): $(obj)
$(cc) -o $(prom) $(obj) %.o: %.c $(deps)
$(cc) -c $< -o $@
  在这里,你可能看不懂最后两行的命令的意思,其实他们是makefile中存在的系统默认的自动化变量。首先是%.o:%.c,这是一个模式规则,表示所有的.o目标都依赖于与它同名的.c文件(当然还有deps中列出的头文件)。再来就是命令部分的$<和$@,其中$<代表的是依赖关系表中的第一项(如果我们想引用的是整个关系表,那么就应该使用$^),具体到我们这里就是%.c。而$@代表的是当前语句的目标,即%.o。这样一来,make命令就会自动将所有的.c源文件编译成同名的.o文件。不用我们一项一项去指定了。

  到目前为止,我们已经有了一个不错的makefile,至少用来维护这个小型工程是没有什么问题了。

1.5清空目标文件的规则

  每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。一般的风格都是:
cc = gcc
prom = calc
deps = calc.h
obj = main.o getch.o getop.o stack.o $(prom): $(obj)
$(cc) -o $(prom) $(obj) %.o: %.c $(deps)
$(cc) -c $< -o $@
.PHONY:clean
clean:
rm -rf $(obj) $(prom)

有了上面最后两行代码,当我们在终端中执行make clean命令时,它就会去删除该工程生成的所有编译文件,从而保持整个工程整洁。.PHONY意思表示clean是一个“伪目标”,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。

1.6使用函数使得makefile更智能

如果我们需要往工程中添加一个.c或.h,可能同时就要再手动为obj常量再添加第一个.o文件,如果这列表很长,代码会非常难看,为此,我们需要用到Makefile中的函数,这里我们演示两个:

cc = gcc
prom = calc
deps = $(shell find ./ -name "*.h")
src = $(shell find ./ -name "*.c")
obj = $(src:%.c=%.o) $(prom): $(obj)
$(cc) -o $(prom) $(obj) %.o: %.c $(deps)
$(cc) -c $< -o $@ clean:
rm -rf $(obj) $(prom)
其中,shell函数主要用于执行shell命令,具体到这里就是找出当前目录下所有的.c和.h文件。而$(src:%.c=%.o)则是一个字符替换函数,它会将src所有的.c字串替换成.o,实际上就等于列出了所有.c文件要编译的结果。有了这两个设定,无论我们今后在该工程加入多少.c和.h文件,Makefile都能自动将其纳入到工程中来。
到这里,整个makefile文件的编写例程就介绍完毕了,你也可以自己尝试着写一个简单的makefile文件进行编译学习。

推荐书籍下载链接

http://download.csdn.net/download/noticeable/10242106

Makefile工具下载

CMake - Cross Platform Make

Automake - GNU Project

SCons: A software construction tool

https://www.lrde.epita.fr/~adl/autotools.html

参考资料

http://blog.csdn.net/ruglcc/article/details/7814546

https://www.gnu.org/software/make/manual/make.html

https://www.zhihu.com/question/23792247

https://yq.aliyun.com/articles/9243

http://blog.csdn.net/liang13664759/article/details/1771246

http://blog.csdn.net/haoel/article/details/2886/

https://www.jianshu.com/p/ff0e0e26c47a

6、GNU makefile工程管理学习的一个例子的更多相关文章

  1. ARM裸机开发之交叉工具链和MakeFile工程管理

    一.交叉工具链 嵌入式Linux开发采用交叉开发,简单来说就是在宿主机(PC机)上面编译出能够在其他硬件平台上面运行的程序.在这个过程中,需要用到许多的交叉工具,这些交叉工具的集合就叫做交叉工具链.下 ...

  2. Makefile 工程管理

    Makefile 工程管理 Makefile 规则 --变量 在Makefile中,用户除了可以自己定义变量外,还可以使用存在系统已经定义好的默认变量 $^:代表所有的依赖文件 $@:代表目标 $&l ...

  3. 1-3 编程基础 makefile工程管理

    GNU make Linux程序员必须学会使用GNU make来构建和管理自己的软件工程.GNU的make能够使整个工程的编译.链接只需要一个命令就可以完成. makefile make在执行时,需要 ...

  4. linux应用程序设计--Makefile工程管理

    Makefile文件描述了整个工程的编译.链接等规则.包括:工程中哪些源文件需要编译以及如何编译:需要创建哪些库文件以及如何创建这些库文件.如何产生最终的可执行文件. Makefile相关术语 1.规 ...

  5. 1.3 Makefile 工程管理

    1. 为什么得用Makefile 单步命令生成led.bin [root@cfm880 lesson1]# cd .. [root@cfm880 Part1]# mkdir lesson3 [root ...

  6. Makefile工程管理器及万能模板

    ​ Linux 环境下的程序员如果不会使用GNU make来构建和管理自己的工程,应该不能算是一个合格的专业程序员,至少不能称得上是 Unix程序员.在 Linux(unix )环境下使用GNU 的m ...

  7. makefile工程管理

    个人理解吧,makefile就是写一个指定格式的文件,将一系列的编译.链接.转换等操作打包在一起,方便以后一键生成可执行的二进制文件而产生的.下面记录一下这种文件的写法,方便以后忘了来查询. make ...

  8. [国嵌笔记][017][Makefile工程管理]

    Makefile的用途 1.make能够使整个程序的编译.链接只需一个命令就可以完成 2.make的工作主要依赖于Makefile的文件.Makefile文件描述了整个程序的编译.链接等规则,使之自动 ...

  9. 学习总结:工程管理与makefile

    工程管理与makefile 一.为什么需要makefile和make 一个工程中的源文件可能很多,按照类型.功能.模块分别放在若干个目录中,为了有效地管理软件工程,更高效地编译整个工程,需要用到mak ...

随机推荐

  1. 3F - Lowest Common Multiple Plus

    求n个数的最小公倍数. Input 输入包含多个测试实例,每个测试实例的开始是一个正整数n,然后是n个正整数. Output 为每组测试数据输出它们的最小公倍数,每个测试实例的输出占一行.你可以假设最 ...

  2. 操作系统之CPU管理的直观想法

    计算机:是工具,帮助解决实际问题 操作系统,是为了方便使用硬件 计算机模型: 图灵机,模拟人类计算 起初的图灵机就像一个只会做一道菜的厨师 通用图灵机,核心是设置控制器动作(修改控制器),把逻辑读入控 ...

  3. 使用RSS订阅

    1.绪论 对某一主题完成一次文献检索后,我们希望能持续了解该主题最新文献,即实现文献追踪. 为此,搜索引擎和数据库厂商(数据源)提供一般两种订阅服务:邮件和RSS.订阅后,数据源会自动推送最新信息,免 ...

  4. Kali Linux 网络扫描秘籍

    第三章 端口扫描(二) 作者:Justin Hutchens 译者:飞龙 协议:CC BY-NC-SA 4.0 3.6 Scapy 隐秘扫描 执行 TCP 端口扫描的一种方式就是执行一部分.目标端口上 ...

  5. iOS.FBTweak

    FBTweak的源码分析 1. FBTweak提供了以下功能 A): 可以动态的修改某个变量的值,这些变量的类型包括: ... B): 可以以plist的形式将Tweak以key-value的形式进行 ...

  6. 洛谷1993 小K的农场

    原题链接 裸的差分约束. \(X_a-X_b\geqslant C\) \(X_a-X_b\leqslant C\Rightarrow X_b-X_a\geqslant -C\) \(X_a-X_b\ ...

  7. Luogu2022 有趣的数-二分答案+数位DP

    Solution 我好像写了一个非常有趣的解法233, 我们可以用数位$DP$ 算出比$N$小的数中 字典序比 $X$ 小的数有多少个, 再和 $rank$进行比较. 由于具有单调性, 显然可以二分答 ...

  8. MySQL学习笔记-锁相关话题

    在事务相关话题中,已经提到事务隔离性依靠锁机制实现的.在本篇中围绕着InnoDB与MyISAM锁机制的不同展开,进而描述锁的实现方式,多种锁的概念,以及死锁产生的原因.   Mysql常用存储引擎的锁 ...

  9. Spring Boot REST(二)源码分析

    Spring Boot REST(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) SpringBoot RE ...

  10. 类名.class 与 类名.this 详解

    类名.class 与 类名.this 详解 今天在看 PropertyPlaceholderConfigurer 源码时,突然看到一个 PropertyPlaceholderConfigurer.th ...