谷动谷力

标题: 【C语言智能编译一步步学习编写Makefile】Makefile介绍 [打印本页]

作者: sunsili    时间: 2018-11-14 22:50
标题: 【C语言智能编译一步步学习编写Makefile】Makefile介绍
本帖最后由 sunsili 于 2023-8-4 13:26 编辑

【C语言智能编译一步步学习编写Makefile】Makefile介绍


本文是学习Makefile的总结与记录,学习自 跟我一起写Makefile 感谢作者与整理者,需要pdf文件请留言,如有错误请及时提出。

在学习Makefile之前,首先介绍一下make命令,make命令是GNU的工程化编译工具,它用于编译大量互相关联的源代码,使用它可以实现项目的工程化管理,提高开发效率。那么对于一个项目,该如何让它按照我们预想的规则去编译链接执行呢?这就要用到我们要学习的Makefile了。Makefile的作用就是在执行make命令的时候指定编译和链接的规则,包括源代码文件之间的链接关系、依赖关系等。它关系到整个项目工程的编译规则:哪些文件需要先编译,哪些要后编译,哪些需要重新编译等复杂的操作。Makefile文件就像shell脚本一样,在其中也可以执行操作系统的命令。

Makefile实现了自动化编译,当然现在有许多的IDE自带了这样的功能,但是,为了提升自己的项目架构能力与工程管理能力,掌握Makefile的编写还是很有必要的。本系列文章学习自陈皓的《跟我一起写Makefile》,使用的是GNU的make,以C/C++源码为基础。本系列文章将不涉及编译器的知识,只介绍基本的编译与链接知识,如要深入了解,请移步我另一系列文章。

程序的编译和链接
对于程序的编译,无论是哪种编译器,首先都需要将源代码文件编译成中间代码(Win下.obj文件,UNIX下.o文件),即Object文件,然后再将大量的Object文件链接在一起进行执行。

在编译时,编译器检查程序代码中语法的正确性,函数以及变量的声明是否正确,以及进行预处理(例如头文件的所在位置以及宏替换等),此步骤只要语法没有问题,编译就不会出错,便可以生成中间目标文件。

在链接时,主要是链接函数与全局变量,链接器不用管函数在哪一个源文件当中,它关心的是函数所在的中间目标文件。大多数情况下,编译生成的中间目标文件比较多,在链接时需要明确地指出中间目标名,这对于编译很不方便,解决方法是给中间目标文件打包生成一个库文件(Win下是.lib,UNIX是.a)。

综上,编译链接的过程是:

Makefile 介绍
我们已经知道,在我们执行make命令时,是根据Makefile文件定义的规则去编译执行的,所以Makefile的书写就规定了程序的执行方式。我们从一个简单示例开始:
假设我们现在的工程有8个.c文件,3个.h头文件。我们需要写一个Makefile来定义该程序的执行方式:

写好Makefile,无论怎么修改源程序,都只需要一个make命令搞定。它会自动根据当前的文件修改的情况来确定哪些文件需要重新编译,从而自己编译所需要的文件和链接目标程序。

Makefile的规则
在着手写Makefile之前,我们来看一下Makefile的规则。
  1. target ... : prerequisites ...
  2.         command
  3.         ...
  4.         ...
复制代码

下面我们就根据介绍的编写规则完成一开始的要求。项目的8个.c文件和.h文件如下:
。。。
先贴上编写好的Makefile文件:
  1. proc : main.o kbd.o command.o display.o \
  2.         insert.o search.o files.o utils.o
复制代码

请注意\代表换行。编写好此文件保存为Makefile或者makefile文件即可。然后在该目录下执行make即可。如果需要删除执行文件与所有的目标文件,直接make clean即可。
根据之前介绍的Makefile的规则,看一下上面的Makefile文件都做了什么:

make是如何工作的
通常我们在命令行知识输入一个make命令,那么,当我们实行make时,它是怎么开始工作的呢?

Makefile中使用变量
在上面编写的Makefile中:
  1. proc main</font><font color="#d19a66">.o</font> <font color="#e06c75">kbd</font><font color="#d19a66">.o</font> <font color="#e06c75">command</font><font color="#d19a66">.o</font> <font color="#e06c75">display</font><font color="#d19a66">.o</font> \ </div><div align="left">             <font color="#e06c75">insert</font><font color="#d19a66">.o</font> <font color="#e06c75">search</font><font color="#d19a66">.o</font> <font color="#e06c75">files</font><font color="#d19a66">.o</font> <font color="#e06c75">utils</font><font color="#d19a66">.o</font></div><div align="left">    <font color="#e06c75">gcc</font> <font color="#e06c75">-o</font> <font color="#e06c75">proc</font> <font color="#e06c75">main</font><font color="#d19a66">.o</font> <font color="#e06c75">kbd</font><font color="#d19a66">.o</font> <font color="#e06c75">command</font><font color="#d19a66">.o</font> <font color="#e06c75">display</font><font color="#d19a66">.o</font> \</div><div align="left">            <font color="#e06c75">insert</font><font color="#d19a66">.o</font> <font color="#e06c75">search</font><font color="#d19a66">.o</font> <font color="#e06c75">files</font><font color="#d19a66">.o</font> <font color="#e06c75">utils</font><font color="#d19a66">.o</font></div>
复制代码

这些.o文件我们写了两次,加入项目中又新加入了一个.c文件,那么对于我们的Makefile,需要好几处都做改动。这样的话难免有点枯燥,而且项目大的话Makefile也会写的很混乱,不方便阅读。因此,我们需要一个变量来代替我们写的这些文件。
比如声明一个变量objects(其它名字也可以)来表示这一大串.o文件:
  1. object = main.o kbd.o command.o display.o \
复制代码

那么就可以使用objects来代表着一系列文件,使用方法如下:
  1. objects = main.o kbd.o command.o display.o \
复制代码

是不是看上去清爽一点了。如果有新的文件,只用修改一下objects,给它添加一个文件即可。关于变量的更多内容将在后面的文章中深入介绍。

让make自动推导
make可以识别并且自动推导命令,它识别一个.o文件,就会自动将对应的.c文件加在依赖关系中。并且也会自动推导出相关的编译命令。有了这个特性,我们的Makefile文件就可以写的更加简洁了:
  1. objects = main.o kbd.o command.o display.o \
复制代码

这种方式称为make的隐晦规则。.PHONY表示clean是一个伪目标文件。对于详细内容将在后续文章中介绍。

另类的Makefile
make可以自动推导命令,这很方便。但是观察我们新的Makefile,有一个地方还是觉得很扎眼,那就是每个.o文件都依赖于很多的.h文件,能不能把这些.h文件综合到一起呢?
看一下这种书写方式:
  1. objects = main.o kbd.o command.o display.o \
复制代码

这样的编写风格使得Makefile文件更加简洁,但是随之带来的是不能直观地看出某个.o文件依赖了哪些文件。所以并不是很推荐。

清空目标文件的规则
对于编写习惯的养成而言,我们最好在每个Makefile中都写一个清空目标文件的clean命令,这样有利用重新编译。例如:
  1. clean :
复制代码

或者:

  1. .PHONY : clean
复制代码


注意,后者的-表示忽略问题,强制执行。而且不要将clean放在文件的开头,因为Makefile默认执行的就是文件开头的target。因此,对于clean,最好将它放在文件最后。







欢迎光临 谷动谷力 (http://bbs.sunsili.com/) Powered by Discuz! X3.2