Linux/Uinx Makefile介绍以及使用方法和代码演示

2024-01-19 01:52

本文主要是介绍Linux/Uinx Makefile介绍以及使用方法和代码演示,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Linux/Uinx Makefile介绍以及使用方法和代码演示

在写完我们的C语言程序之后,我们通常可以使用shell脚本来编译和链接C语言程序的源文件,但是这种方式有一个缺点:当我们更改了几个源文件的名称之后,shell脚本就会编译失败,为了解决这种问题,我们可以使用Makefile,自动有选择的执行编译链接。

makefile 格式


一个make文件由一系列目标项、依赖项、规则组成。

下面是makefile的格式:

目标项依赖项列表
target:file1 file2 … fileN
规则
<tab>command1
<tab>command2
<tab>other command

目标项通常是需要创建或者更新的文件,但他也可能是make程序要引用的指令标签

依赖项是目标项的提前执行项,也就是说如果一个依赖项比目标项新(也就是说,依赖项的修改时间在目标项之后),或者目标项不存在,那么make就会执行对应的命令来生成或者更新目标项。这种机制可以确保当你修改了一部分源代码后,只有依赖于这部分源代码的目标会被重新编译,而其他不受影响的目标则不会被重新编译。这样可以大大提高编译的效率。

依赖项列表执行示例

假定有三个文件:type.h mysum.c t.c

我们通过以下的方式使用makefile去编译链接这些文件:

myt: type.h t.c mysum.c				#依赖项列表,目标项名称为myt,依赖项为type.h, t.c, mysum.cgcc -o myt t.c mysum.c 		#链接规则

生成的可执行文件名(在例子中是myt)通常与目标项名称匹配。这样可以使得make通过目标项时间戳与依赖项时间戳之间的比较,来决定稍后是否再次构建目标项。

使用mk1作为makefile文件,并且使用make指令去运行该文件:

make -f mk1

make将会构建目标文件(.o文件),并且将命令执行显示为:

gcc -o myt t.c mysum.c

如果此时再次运行make,将会看到信息:

 make: 'myt' is up to date

这将会意味着make程序不会执行,因为根据makefile文件中目标项和依赖项的时间戳的比较,目标项myt是时间戳中最后一个更改的,因此说明myt已经是根据其他依赖项编译成功的了,无需重新编译。

如果我们从依赖项列表中删除一些文件名称,即使这些文件有更改,make也不会执行rule命令。

原因仍然是时间戳的关系,因为依赖项中的文件少了=当这些文件的时间戳不能被检测

makefile中的宏

在makefile中,宏定义的符号——$被替换为他们的值。

CC = gcc										#将CC替换为GCCCFLAGS = -Wall									#将CFLAGS替换为-WallOBJS = t.o mysum.o								#将OBJS替换为t.o mysum.oINCLUDE = -Ipath								#将将INCLUDE替换为-Ipath,这里其实是告诉编译器在path路径下查找头文件的路径myt: type.h $(OBJS)								#创建了一个目标文件type.h,依赖项包括t.o mysum.o$(CC) $(CFLAGS) -o t $(OBJS) $(INCLUDE)		#gcc -Wall -o t t.o mysum.o path

执行之后出现下面的效果:

gcc -Wall   -c -o solve.o solve.c
gcc -Wall   -c -o myt.o myt.c
gcc -Wall -o t solve.o myt.o -Ipath

对于依赖项列表中的每个.o文件,make首先会将相应的.c文件编译成.o文件(这里你可能会有疑问,这个问题放到本段最后去讨论),但是!这只会适用于.c文件,因为所有的.c文件都依赖于.h文件,所以必须在依赖项列表中显式地包含type.h(当然,其他.h文件也是这样的),不过也有其他解决办法,如下所示

t.o: t.c type.hgcc -c t.c
mysum.o: mysum.c type.hgcc -c mysum.c

如果我们将上述目标项添加到makefile文件中,.c文件或者type.h中的任何更改都将触发make重新编译.c文件。

但是这种方法在工程量巨大的时候会非常繁琐,因此还有其他办法。

你可能会有的疑问

在上面的文章的示例中,我们写了这样的代码:

CC = gcc										#将CC替换为GCCCFLAGS = -Wall									#将CFLAGS替换为-WallOBJS = t.o mysum.o								#将OBJS替换为t.o mysum.oINCLUDE = -Ipath								#将将INCLUDE替换为-Ipath,这里其实是告诉编译器在path路径下查找头文件的路径myt: type.h $(OBJS)								#创建了一个目标文件type.h,依赖项包括t.o mysum.o$(CC) $(CFLAGS) -o t $(OBJS) $(INCLUDE)		#gcc -Wall -o t t.o mysum.o path

但是你可能会有一个问题:我的目录中没有.o文件,我也没有去写对应的规则,为什么它会知道自己去编译同名的.c文件呢?

这是因为Makefile中的规则和隐含规则。在Makefile中,指定了myt依赖于mysum.ot.o。当你运行make myt时,make会检查mysum.ot.o是否存在,如果不存在,它会查找能够生成这些文件的规则。

在我们的Makefile中并没有明确的规则来生成solve.omyt.o,但是make有一套默认的隐含规则。其中一个隐含规则是,如果一个.o文件不存在,make会查找同名的.c文件,然后使用C编译器(在你的Makefile中定义为gcc)来编译这个.c文件,生成对应的.o文件。

所以,即使你的Makefile中并没有明确指定如何从.c文件生成.o文件,make也能通过它的隐含规则自动完成这个过程。

按名称编译目标

什么是按名称编译目标呢?这里的名称并不是指的.c文件或者.o文件的名称,而是指的目标项的名称,以如下程序举例:

假设有下面三个文件:

solve.c:

#include <stdio.h>int solve(int a, int b) {return (a - b) * b;
}

myso.h:

#ifndef _MYSO_H
#define _MYSO_H#include <stdio.h>int solve(int a, int b);#endif

myt:包含main函数的文件

#include <stdio.h>
#include "myso.h"int main() {int a, b;scanf("%d %d", &a, &b);printf("%d", solve(a, b));return 0;
}

那么关于上面三个文件的makefile如下所示:

#makefile
CC = gcc
CFLAGS = -Wall
OBJSC = solve.o myt.o
INCLUDE = -Ipathall: myt installmyt: myso.h $(OBJSC)$(CC) $(CFLAGS) -o myt $(OBJSC) $(INCLUDE)solve.o: solve.c myso.hgcc -c solve.cmyt.o: myt.c myso.hgcc -c myt.cinstall: mytecho install myt to /usr/local/binsudo mv myt /usr/local/bin/run: installecho run executable image mytmyt || /bin/trueclean:rm -f *.o 2> /dev/nullsudo rm -f ./usr/local/bin/myt

接下来对上面的程序中的目标项做出分析:

  • all:这个目标项包括两个依赖项mytinstall,当运行这个目标项时
    (指令为make all makefile),会直接执行mytinstall两个目标项
  • myt:正常编译链接指令,生成的文件名称为myt,这里不再解释
  • solve.omyt.o:可有可无,对myt中的依赖项的补充,其实可以由make程序自带的隐式规则自动执行
  • install:将生成的myt文件转移至/usr/local/bin/目录,这个目录中通常放置着用户安装的可执行文件,在系统变量PATH中,该目录地址被包含,因此放在这个目录中的可执行文件可以在terminal中直接执行
  • run:只有install执行之后才会执行,执行myt指令,如果执行成功则结束,如果执行失败就执行/bin/true确保命令的返回值为0,这样可以防止myt运行失败导致整个makefile运行失败
  • clean:删除掉所有.o文件还有可执行文件,包括系统目录中(/usr/local/bin/)对应名称的可执行文件

执行上面的程序时有以下几种方式:

  • make all -f makefile
  • make myt -f makefile
  • make install -f makefile
  • make run -f makefile
  • make clean -f makefile

其功能详细读者已经能够心领神会,这里不再多做解释

makefile 变量 和 后缀规则

makefile支持变量,在makefile中,%是一个与sh中的*类似的通配符变量。

makefile还包含自动变量,这些变量在匹配规则后由make设置,自动变量规定了对目标和依赖项列表中元素的访问(也就是说可以通过自动变量在规则中来表示依赖项名称),下面是makefile的一些自动变量

  • $@:当前目标名
  • $<:第一个依赖项名
  • $^:所有依赖项名
  • $*:不包含扩展名的当前依赖项名
  • $?:比当前目标更新的依赖项列表

make也支持后缀规则,后缀规则并非目标,而是make程序的指令,我们通过一个例子来了解make变量后缀规则

DEPS = type.h
%.o: %.c $(DEPS)$(CC) -c -o $@

在上面的程序中,%.o代表所有.o文件$@表示设置为当前目标名称,这样可以避免为单个.o文件定义单独的目标。

接下来再举一个后缀规则的例子:

CC = gcc			#编译器
CFLAGS = -I.		#表示搜索路径包含当前目录
OBJS = myt.o solve.o
AS = as				#汇编器
DEPS = type.h.s.o:$(AS) -o $< -o $@	#.s->.o的规则
.c.o:$(CC) -c $< -o $@	#.c->.o的规则
%.o: %.c $(DEPS)$(CC) -c -o $@ $<
myt: $(OBJS)$(CC) $(CFLAGS)	-o $@ $^

其中需要解释的有以下几点:

  • AS = as :设置变量AS的值为as,表示使用as作为汇编器
  • DEPS = type.h设置变量DEPS的值为type.h,表示依赖的头文件

然后定义了几个规则:

  • .s.o:这是一个后缀规则,表示如何从.s文件生成.o文件。规则的内容是$(AS) -o $< -o $@,表示使用汇编器as将源文件(<)编译为目标文件(@)。
  • .c.o:这是另一个后缀规则,表示如何从.c文件生成.o文件。规则的内容是$(CC) -c $< -o $@,表示使用C编译器gcc将源文件(<)编译为目标文件(@)。
  • %.o: %.c $(DEPS):这是一个模式规则,表示如何从.c文件和依赖的头文件生成.o文件。规则的内容是$(CC) -c -o $@ $<,表示使用C编译器gcc将源文件(<)编译为目标文件(@)。

这些规则指定:对于每一个.o文件,如果他们的时间戳不同,即.s或者.o文件已经修改,则应当立即创建一个与之对应的.s或者.o文件。

最后定义了一个目标:

  • myt: $(OBJS):这是一个目标,表示如何从目标文件生成可执行文件myt。规则的内容是$(CC) $(CFLAGS) -o $@ $^,表示使用C编译器gcc和编译选项-I.将所有的目标文件链接为可执行文件myt(@)。

嵌套makefile

大型的C语言变成项目通常由数十到数百个源文件组成。为了便于维护,源文件通常被放到不同级别的目录中,每个目录都有自己的makefile。make很容易进入子目录以通过命令执行该目录中的本地makefile:

(cd DIR; $(MAKE)) OR cd DIR && $(MAKE)

关于这个指令,具有如下值得在意的解释:

  • (cd DIR; $(MAKE)):这个命令会在一个子shell中执行。首先,它会切换到DIR目录,然后在该目录中执行Makefile文件。执行完毕后,它会返回到原来的目录。这个命令的优点是不会改变当前shell的工作目录,但是如果cd DIR命令失败,它仍然会尝试执行$(MAKE)命令。

  • cd DIR && $(MAKE):这个命令会在当前的shell中执行。它首先会切换到DIR目录,如果成功,然后在该目录中执行Makefile文件。如果cd DIR命令失败,它不会执行$(MAKE)命令。这个命令的优点是如果cd DIR命令失败,它不会尝试执行$(MAKE)命令,从而避免可能的错误。但是,它会改变当前shell的工作目录

废话不多说,上例子:
假设你有一个项目,它的目录结构如下:

/myprojectMakefile/srcMakefilemain.c/libMakefilemylib.c

在这个项目中,src目录和lib目录都有自己的Makefile文件。你可以在顶级目录的Makefile文件中使用make命令来调用子目录中的Makefile文件,如下所示:

# /myproject/Makefile
all:make -C srcmake -C lib

在这个Makefile文件中,make -C src命令会切换到src目录,并调用该目录中的Makefile文件。同样,make -C lib命令会切换到lib目录,并调用该目录中的Makefile文件。

当然还有其他的两种写法:

# /myproject/Makefile
all:(cd src; $(MAKE))(cd lib; $(MAKE))

或者

# /myproject/Makefile
all:cd src && $(MAKE)cd lib && $(MAKE)

如果子文件中的makefile文件是其他名称怎么办呢?可以这样写:

  1. make -C DIR -f MyMakefile
  2. (cd DIR; $(MAKE) -f MyMakefile)
  3. cd DIR && $(MAKE) -f MyMakefile

这三种方法都可以用来在子目录中执行具有特定名称的Makefile文件。希望这个答案能帮助你。

然后,在src目录和lib目录中的Makefile文件中,你可以定义如何编译该目录中的源文件,例如:

# /myproject/src/Makefile
CC = gcc
CFLAGS = -I../lib
OBJS = main.o%.o: %.c$(CC) $(CFLAGS) -c $< -o $@main: $(OBJS)$(CC) $(CFLAGS) -o $@ $^
# /myproject/lib/Makefile
CC = gcc
CFLAGS = -I.
OBJS = mylib.o%.o: %.c$(CC) $(CFLAGS) -c $< -o $@mylib: $(OBJS)$(CC) $(CFLAGS) -o $@ $^

在这两个Makefile文件中,我们定义了如何从.c文件生成.o文件,以及如何从.o文件生成可执行文件或库文件。

在顶级目录中运行make命令时,make工具会自动编译和链接所有的源文件,无论它们在哪个子目录中。


以上就是本文章的所有内容啦~~~创作不易,希望多多点赞、收藏、关注!!!!
欢迎评论区提问!!有交流才会有进步!!!

这篇关于Linux/Uinx Makefile介绍以及使用方法和代码演示的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/620938

相关文章

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

Python安装Pandas库的两种方法

《Python安装Pandas库的两种方法》本文介绍了三种安装PythonPandas库的方法,通过cmd命令行安装并解决版本冲突,手动下载whl文件安装,更换国内镜像源加速下载,最后建议用pipli... 目录方法一:cmd命令行执行pip install pandas方法二:找到pandas下载库,然后

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Linux如何查看文件权限的命令

《Linux如何查看文件权限的命令》Linux中使用ls-R命令递归查看指定目录及子目录下所有文件和文件夹的权限信息,以列表形式展示权限位、所有者、组等详细内容... 目录linux China编程查看文件权限命令输出结果示例这里是查看tomcat文件夹总结Linux 查看文件权限命令ls -l 文件或文件夹

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

idea的终端(Terminal)cmd的命令换成linux的命令详解

《idea的终端(Terminal)cmd的命令换成linux的命令详解》本文介绍IDEA配置Git的步骤:安装Git、修改终端设置并重启IDEA,强调顺序,作为个人经验分享,希望提供参考并支持脚本之... 目录一编程、设置前二、前置条件三、android设置四、设置后总结一、php设置前二、前置条件