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

本文介绍了Makefile在C语言项目中的使用,包括其格式、目标项与依赖项、规则的编写,以及宏、隐含规则、后缀规则和嵌套Makefile的运用,展示了如何通过Makefile提高编译效率。
摘要由CSDN通过智能技术生成

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.c
		gcc -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替换为GCC

CFLAGS = -Wall									#将CFLAGS替换为-Wall

OBJS = t.o mysum.o								#将OBJS替换为t.o mysum.o

INCLUDE = -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.h
	gcc -c t.c
mysum.o: mysum.c type.h
	gcc -c mysum.c

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

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

你可能会有的疑问

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

CC = gcc										#将CC替换为GCC

CFLAGS = -Wall									#将CFLAGS替换为-Wall

OBJS = t.o mysum.o								#将OBJS替换为t.o mysum.o

INCLUDE = -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 = -Ipath

all: myt install

myt: myso.h $(OBJSC)
	$(CC) $(CFLAGS) -o myt $(OBJSC) $(INCLUDE)

solve.o: solve.c myso.h
	gcc -c solve.c

myt.o: myt.c myso.h
	gcc -c myt.c

install: myt
	echo install myt to /usr/local/bin
	sudo mv myt /usr/local/bin/

run: install
	echo run executable image myt
	myt || /bin/true

clean:
	rm -f *.o 2> /dev/null
	sudo 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的工作目录

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

/myproject
    Makefile
    /src
        Makefile
        main.c
    /lib
        Makefile
        mylib.c

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

# /myproject/Makefile
all:
    make -C src
    make -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工具会自动编译和链接所有的源文件,无论它们在哪个子目录中。


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

  • 29
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若亦_Royi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值