Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >换个角度说Makefile

换个角度说Makefile

作者头像
编程珠玑
发布于 2020-02-11 08:43:58
发布于 2020-02-11 08:43:58
77800
代码可运行
举报
文章被收录于专栏:编程珠玑编程珠玑
运行总次数:0
代码可运行

来源:公众号【编程珠玑】

作者:守望先生

ID:shouwangxiansheng

作为Linux下的C/C++开发者,没接触过makefile一定说不过去,通常构建大型的C/C++项目都离不开makefile,也许你使用的是cmake或者其他类似的工具,但它们的本质都是类似的。

作为一个轻度使用者,应读者要求,斗胆介绍一下makefile,不过与普通的makfile教程不同的是,本文准备从另外一个角度来介绍。如有不妥之处,欢迎指出。

makefie到底是什么

在Linux下,对于下面这个简单的程序

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//来源:公众号【编程珠玑】
//main.c
#include <stdio.h>
#include <math.h>
int main()
{
    int a = 10;
    int b = 4;
    int c = pow(a,b);
    printf("10^4 = %d",c);
    return 0;
}

我们通常使用gcc就可以编译得到想要的程序了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ gcc -o main main.c -lm

(如果不理解为什么要加-lm,请参考《一个奇怪的链接问题》)。

对于单个文件的简单程序,一条命令就可以直接搞定了(编译+连接),但是如果是一个复杂的工程,可能有成千上万个文件,然后需要链接大量的动态或静态库。试想一下,你还想一条一条命令执行吗?懒惰的基因是刻在程序员骨子里的。

因此你可能会想,那我写个脚本好了。嗯,听起来好多了。

文件多就多,你告诉我要编译哪里的文件,我遍历一下就好了,你再告诉我要链接哪些库,我一一帮你链接上就好了。

然而到这里又会想,既然编译链接都是这么类似的过程,能不能给它们写一些通用的规则,搞得这么复杂干嘛?然后按照规则去执行就好了。

而makefile就是这样的一个规则文件,make是规则的解释执行者。可以类比shell脚本和bash解释程序的关系。

所以,makefile并不仅仅用于编译链接,只不过它非常适合用于编译链接。

makefile什么样?

它最重要的规则语法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<target> : <prerequisites> 
[tab]  <commands>

咋一看,就这么个玩意?但是什么意思?

  • target 要生成的目标文件名称
  • 要依赖的文件
  • [tab] 对,就是tab键,初学者很容易忽略这个问题,请用tab
  • 要执行的指令

关键内容就这些,但是要细讲会有很多内容,本文仅举个简单的例子。假设要将前面的main.c复制名为pow.c的文件。 那么我们可以得到:

  • target: pow.c 目标名称
  • prerequisites:main.c,即得到pow.c需要有main.c
  • commands:cp main.c pow.c

因此我们得到我们的makefile文件内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
pow.c:main.c
    cp main.c pow.c
clean:
    rm pow.c

假设当前目录下没有main.c文件,然后在当前目录下执行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ make pow.c
make: *** No rule to make target `main.c', needed by `pow.c'.  Stop.

我们发现会报错,因为你要依赖的文件找不到,而且也没有其他规则能够生成它。

现在把main.c放在当前目录下后继续执行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ make 
cp main.c pow.c

看见没有,执行完make命令之后,我们的pow.c文件终于有了。

而执行下面的命令后:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ make clean
rm pow.c

你就会发现pow.c被删除了。

如果当前目录有clean文件会发生什么?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ make clean
make: `clean' is up to date.

至于原因,后面会讲到。

这里注意,如果你的makefile文件的文件名不是makefile,那么就需要指定具体名字,例如假设前面的文件名为test.txt:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ make -f test.txt

以上例子介绍了makefile使用的基本流程,生成目标,清除目标。然而实际上这里面的门道还有很多,例如伪目标,自动推导,隐晦规则,变量定义。本文作为认识性的文章暂时不具体介绍。

总结来说就是,给规则,按照规则生成目标。

makefile做了什么?

网上有很多教程介绍如何编写makefile的,很多也非常不错。不过本文换个角度来说。

既然我们要学makefile,那么就需要知道构建C/C++项目的时候,它应该做什么?然后再去学习如何编写makefile。

实际上它主要做的事情也很清楚,那就是编译和链接。这个在《helo程序是如何编程可执行文件的》中已经有所介绍,还不了解的朋友可以简单了解一下。那么放到makefile中具体要做什么呢?

  • 将源代码文件编译成可重定位目标文件.o(参考《静态库和动态库的区别》)
  • 设置编译器选项,例如是否开启优化,传递宏,打开警告等
  • 链接,将静态库或动态库与目标文件链接

所以问题就变成了,如何利用makefile的语法规则快速的将成千上万的.c编译成.o,并且正确链接到需要的库。

而如果用makefile应该怎么写才能得到我们的程序呢?为了帮助说明,我们把前面的编译命令拆分为两条:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ gcc -g -Wall -c main.c -o main.o
$ gcc -o main main.o -lm
设置编译器

由于我们使用的是gcc编译器(套件),因此可以像下面这样写:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CC=gcc

为了扩展性考虑,常常将编译器定义为某个变量,后面使用的时候就会方便很多。

设置编译选项

比如我们要设置-g选项用来调试,设置-Wall选项来输出更多警告信息。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CFLAGS=-g -Wall
设置链接库

我们这里只用到了libm.so库

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
LIBS=-lm
编译

我们的目标文件是main.o依赖main.c,该规则应该是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
OBJ=main.o
$(OBJ):main.c
    $(CC) $(CFLAGS) -c main.c -o $(OBJ)

这样就得到了我们的目标文件。

链接

接下来就需要将目标文件和库文件链接在一起了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
TARGET=main
$(target):main.o
    $(CC) $(CFLAGS) -o $(TARGET) $(OBJ) $(LIBS)

而为了使用make clean,即通常用于清除这些中间文件,因此需要加一个伪目标clean:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.PHONY:clean
clean:
    rm $(OBJ) $(TARGET)

伪目标的意思是,它不是一个真正的要生成的目标文件,.PHONY:clean说明了clean是一个伪目标。在这种情况下,即使当前目录下有clean文件,它也仍然会执行后面的指令。

否则如果当前目录下有clean文件,将不会执行rm动作,而认为目标文件已经是最新的了。

完整内容
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
CC=gcc
CFLAGS=-g -Wall
LIBS=-lm
OBJ=main.o
$(OBJ):main.c
    $(CC) $(CFLAGS) -c main.c -o $(OBJ)
TARGET=main
$(TARGET):main.o
    $(CC) $(CFLAGS) -o $(TARGET) $(OBJ) $(LIBS)
.PHONY:clean
clean:
    rm $(OBJ) $(TARGET)

可以看到,makefile文件中有三个目标,分别是main.o,main和clean,其中clean是一个伪目标。

注意,由于第一个目标是main.o,因此你单单执行make的时候,它只是会生成main.o而已,如果你再执行一次会发现它提示你说main.o已经是最新的了:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ make
gcc -g -Wall -c main.c -o main.o
$ make
make: `main.o' is up to date.

为了得到main,我们执行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ make main
gcc -g -Wall -c main.c -o main.o
gcc -g -Wall -o main main.o -lm
$ ls 
main  main.c  main.o  makefile

当然你也可以调整目标顺序。这里的目标文件main依赖的是main.o,它开始会去找main.o,发现这个文件也没有,就会看是不是有规则会生成main.o,欸,你还别说,真有。main.o又依赖main.c,也有,最终按照规则就会先生成main.o,然后生成mian。

如果要清除这些目标文件,那么可以执行make clean:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ make clean
rm main.o main
$ ls 
main.c  makefile

总结

本文主要介绍了两部分内容。

makefile是什么东西

它是一个规则文件,里面按照某种语法写好了,然后使用make来解释执行,就像shell脚本要用bash解释运行一样。通常会用makefile来构建C/C++项目。

构建C/C++项目的makefile做了什么

makefile主要做下面的事情(以C程序为例)

  • 用变量保存各种设置项,例如编译选项,编译器,宏,包含的头文件等
  • 把.c编译成.o
  • 把.o与库进行链接
  • 清除生成的文件
  • 安装程序

其中最关键的事情就是编译链接,即想办法把.c变成.o(可重定位目标文件);.o+.so(动态库)+.a(静态库)变成可执行文件。

对于文本提到的例子,看起来实在有些笨拙,一条指令搞定,却要写这么多行的makefile,但是它却指出了通常编写makefile的基本思路。

对于一个复杂的项目而言,makefile还有很多东西可介绍,例如如何设置变量,如何交叉编译,如何多个目录编译,如何自动推导,如何分支选择等等。这些都是后话了。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程珠玑 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Makefile常用模板「建议收藏」
做开发的特别是C/C++开发者一般都会用到Makefile,然而多数时候在公司做项目都不需要自己去写Makefile,仅仅停留在能看懂的基础上。因为make命令编译实在是太方便了,所以自己写写小项目用一用还是挺好的。网上关于Makefile教程也很多,总结一下近几年用到的常用脚本,方便自己查阅,如果能帮到别人那便是极好的_。
全栈程序员站长
2022/09/02
1.4K0
makefile终极奥义
或许很多Winodws 的程序员都不知道这个东西,因为那些 Windows 的 IDE 都为你做了这个工作,但是一个好的和 professional 的程序员, makefile 还是要懂。这就好像现在有这么多的 HTML 的编辑器,但如果你想成为一个专业人士,你还是要了解 HTML 的标识的含义。特别在 Unix 下的软件编译,你就不能不自己写 makefile 了,「会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力」。因为 makefile 关系到了整个工程的编译规则。
DeROy
2021/02/05
1.4K0
6_Makefile与GCC
​ 简单地说,我们在PC机上编译程序时,这些程序是在PC机上运行的。我们想让一个程序在ARM板子上运行,怎么办?
韦东山
2022/05/05
3.7K0
6_Makefile与GCC
[Linux工具] Makefile
Linux的make程序用来自动化编译大型源码,很多时候,我们在Linux下编译安装软件,只需要敲一个make就可以全自动完成,非常方便。
DevKevin
2025/01/03
2010
Linux中Makefile文件详解
Makefile 是一个用于构建和管理项目的工具,特别适用于 C/C++ 项目。它定义了项目中各个文件之间的依赖关系,并指定了如何编译和链接这些文件。以下是一个简单的 Makefile 文件的示例,以及对其中关键部分的详细解释:
Linux兵工厂
2024/02/17
1.2K0
Linux中Makefile文件详解
makefile 基础、进阶及常用 makefile
1、若想生成目标,检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来生成该依赖文件 2、检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任一个被更新,则目标必须更新
我与梦想有个约会
2023/10/21
4930
深入掌握 Makefile 与 Make 工具:高效管理自动化编译的核心原理和最佳实践
make 是一个在 Unix/Linux 系统中广泛使用的构建工具,用于自动化编译和构建项目。make 命令通过读取一个名为 Makefile 或 makefile 的文件,根据其中定义的规则执行各种任务(如编译、链接等),从而简化和自动化项目的构建过程。
Srlua
2024/10/30
6990
深入掌握 Makefile 与 Make 工具:高效管理自动化编译的核心原理和最佳实践
Makefile学习1
Makefile是在Linux环境下 C/C++ 程序开发必须要掌握的一个工程管理文件。当你使用make命令去编译一个工程项目时,make工具会首先到这个项目的根目录下去寻找Makefile文件,然后才能根据这个文件去编译程序。
用户9645905
2023/10/17
4440
makefile从入门到放弃——博主吐血整理的笔记
在没有编写makefile之前可以使用命令gcc *.c -Wall查看当前代码是否有语法错误。检查没有语法错误以后可以编写makefile文件。
全栈程序员站长
2022/09/05
1.8K0
Linux工具入门:make工具与Makefile文件
1. make工具 利用make工具可以自动完成编译工作,这些工作包括: 如果修改了某几个源文件,则只重新编译这几个源文件 如果某个头文件被修改了,则重新编译所有包含该头文件的源文件 利用这种自动编译可以大大简化开发工作,避免不必要的重新编译。make工具通过一个称为Makefile的文件来完成并自动维护编译工作,Makefile文件描述了整个工程的编译、连接规则。 2. Makefile文件 Makefile描述了整个工程的编译连接规则。Makefile的基本规则为: TARGET...: DEPENDE
Tencent JCoder
2018/07/02
3.5K0
实战Makefile前,该知道那些知识?
在前一篇文章讲解了Makefile的一些概念和原理,接下来说说Makefile的一些知识点。
Rice加饭
2022/05/10
5150
Makefile总结
Makefile是一个规定了怎么去编译和链接程序的脚本文件,在执行make命令时会执行该文件,window环境下的IDE,如visual studio已经集成了该功能,不需要关心程序的编译规则,在linux下做C/C++开发时经常用到,会写Makefile是程序员的必备技能。说到这里首先要知道一个工具make。
全栈程序员站长
2022/08/30
1K0
Makefile的实战例子
前面我们对Makefile的知识点进行描述,现在给出一个例子,来看看如何使用,顺便结束Makefile这个话题。
Rice加饭
2022/05/10
5230
多文件目录Makefile的写法
  linux下程序开发,涉及到多个文件,多个目录,这时候编译文件的任务量比较大,需要写Makefile
杨永贞
2020/08/04
4.1K0
多文件目录Makefile的写法
超清晰的makefile解释、编写与示例
Makefile范例教学 Makefile和GNU make可能是linux世界里最重要的档案跟指令了。编译一个小程式,可以用简单的command来进行编译;稍微复杂一点的程式,可以用shell script来帮忙进行编译。如今的程式(如Apache, Linux Kernel)可能动辄数百万行程式码,数万个标头档(headers)、库库(libraries)以及程式码(source code),如果只是针对几个档案进行修改,却要用shell script整个程式重新编译,不但浪费时间也相当没有效率。GNU
老白
2018/03/19
5.2K0
Linux — Makefile的学习笔记以及多级目录下Makefile的编写
1、由于Makefile中对于制表符(tab)、还有unix和windows中对于换行符的不同等等原因,本文中所有的Makefile文件中的内容不建议您直接复制然后粘贴使用,如果可以手动敲入是为最佳。如果出现在make的时候出现异常,请详细检查并关注文件格式以及内容编写的格式等等。
全栈程序员站长
2022/09/06
5.9K0
Linux — Makefile的学习笔记以及多级目录下Makefile的编写
Make
###一、make的功能: make是一个用来维护程序模块关系和生产可执行文件的工具,他可以根据程序修改的情况重新编译链接生成的中间代码或最终的可执行文件。执行make命令需要一个Makefile文件,来定义整个项目的编译规则。makefile定义了模块间的依赖关系,指定文件的编译顺序,以及编译所使用的命令。有了make和Makefile文件,整个项目的源程序可以自动编译,极大的提高了软件开发效率。 ###二、Make的一般使用: 1、Makefile的基本构成: Makefile由规则构成,一条规则生成一
宅蓝三木
2018/02/07
2.1K0
Makefile教程
Makefile定义了软件开发过程中,项目工程编译链、链接的方法和规则。 由IDE自动生成或者开发者手动书写。 Unix(MAC OS、Solaris)和Linux(Red Hat、Ubuntu、SUSE)系统下由make命令调用当前目录下的Makefile文件,实现项目工程的自动化编译。
恋喵大鲤鱼
2018/08/03
4.2K0
makefile
target是一个object file,可以是一个执行文件,也可以是一个标签preerquisites是依赖的文件command是shell命令
Heeler-Deer
2023/02/22
1.1K0
make命令和makefile文件
  make命令和makefile文件的结合提供了一个在项目管理领域十分强大的工具,它不仅常被用于控制源代码的编译,而且还用于手册页的编写以及将应用程序安装到目标目录。
全栈程序员站长
2022/07/18
2.7K0
相关推荐
Makefile常用模板「建议收藏」
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验