【c/c++学习】makefile学习

2022-07-13 12:49:45

1、为什么要使用Makefile

怎么高效地编译程序?想达到什么样的效果?请参考Visual Studio:修改源文件或头文件,只需要重新编译牵涉到的文件,就可以重新生成文件。

命令:makefile或者Makefile

2、规则

2.1、1 个规则

目标:依赖条件
(一个tab缩进)命令

(1)目标的时间必须晚于依赖条件的时间,否则,更新目标;

(2)依赖条件如果不存在,找寻新的规则去产生依赖条件。

ALL:指定 makefile 的终极目标。

2.2、2个函数

wildcard和patsubst

src = $(wildcard ./*.c)
    匹配当前工作目录下的所有.c 文件。将文件名组成列表,赋值给变量 src。

obj = $(patsubst %.c, %.o, $(src))
    将参数3中,包含参数1的部分,替换为参数2。
clean:	(没有依赖)

(一个tab)-rm -rf $(obj) a.out	“-”:作用是,删除不存在文件时,不报错。顺序执行结束。

2.3、3 个自动变量

$@: 在规则的命令中,表示规则中的目标。

$^: 在规则的命令中,表示所有依赖条件。

$<: 在规则的命令中,表示第一个依赖条件。如果将该变量应用在模式规则中,它可将依赖条件列表中的依赖依次取出,套用模式规则。

模式规则:

%.o:%.c
    gcc -c $< -o %@

静态模式规则

$(obj):%.o:%.c
    gcc -c $< -o %@

伪目标

.PHONY: clean ALL

参数:

-n:模拟执行make、make clean 命令。

-f:指定文件执行 make 命令。 xxxx.mk

3、实现

下面来一步一步升级makefile

第一个版本的Makefile

makefile的依赖是从上至下的,换句话说就是目标文件是第一句里的目标,如果不满足执行依赖,就会继续向下执行。如果满足了生成目标的依赖,就不会再继续向下执行了。 make会自动寻找规则里需要的材料文件,执行规则下面的行为生成规则中的目标。

编写一个输出hello world的c文件,再编写makefile文件,如下

hello是目标,hello.c是依赖,gcc hello.c -o hello是具体的规则

调整

第1行:hello是目标,hello.o是依赖,gcc hello.o -o hello是具体的规则;

第4行:hello.o是目标,hello.c是依赖,gcc hello.c -o hello.o是具体的规则

这其实是把第一个分开写了。

执行make指令

 继续修改hello.c,如下

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int add (int ,int);
int sub (int ,int);
int div1 (int ,int);


int main(int argc, char* argv[])
{
    int a = 20, b = 10;
    printf("%d + %d = %d \n",a, b, add(a, b));
    printf("%d - %d = %d \n",a, b, sub(a, b));
    printf("%d / %d = %d \n",a, b, div1(a, b));
    printf("%d * %d = %d \n",a, b, mul(a, b));
    return 0;
}

add、sub、div1分别是加法、减法、除法。

add.c

int add(int a, int b)
{
    return a + b;
}

sub.c 和div1.c类似,不再赘述。

此时要进行编译,则需要多文件联合编译:

gcc hello.c add.c sub.c div1.c -o a.out

对这个新的代码,写出下面的makefile

执行make

此时,修改add.c为下图

 此时,再使用make,发现了问题

 可以看到,只修改add.c,但是编译的时候,其他.c文件也重新编译了,这不太灵活。明明只改了一个,全部都重新编译了。

再修改makefile,先生成.o文件

于是将makefile改写如下:

 执行make指令如下

 此时修改sub为下图:

再次make

可以看到,只重新编译了修改过的sub.c和最终目标

makefile检测原理:

修改文件后,文件的修改时间发生变化,会出现目标文件的时间早于作为依赖材料的时间,出现这种情况的文件会重新编译。

修改sub.c后,sub.o的时间就早于sub.c ,a.out的时间也早于sub.o的时间了,于是重新编译这俩文件了。

关于makefile指定目标问题,先修改makefile如下:

 只是将hello放在了文件末尾

执行make,如下:

 这是因为,makefile默认第一个目标文件为终极目标,生成就跑路,这时候可以用ALL来指定终极目标。

指定目标的makefile

 执行make

第一个版本的makefile

ALL:hello
hello.o:hello.c
        gcc -c hello.c -o hello.o

add.o:add.c
        gcc -c add.c -o add.o

sub.o:sub.c
        gcc -c sub.c -o sub.o

div1.o:div1.c
        gcc -c div1.c -o div1.o

hello:hello.o add.o sub.o div1.o
        gcc hello.o add.o sub.o div1.o -o hello

继续升级makefile。

下来介绍两个函数:

src = $(wildcard *.c)

找到当前目录下所有后缀为.c的文件,赋值给src

wildcard函数名

*.c函数参数

src:返回值

obj = $(patsubst %.c,%.o, $(src))

把src变量里所有后缀为.c的文件替换成.o

用这两个函数修改makefile如下:

执行,make指令,如下所示:

 第二个版本的makefile

src = $(wildcard *.c)                    #add.c sub.c div1.c hello.c

obj = $(patsubst %.c, %.o,  $(src))      #把参数3中包含 参数1 的替换成 参数2  add.o sub.o div1.o hello.o


ALL:hello
hello.o:hello.c
        gcc -c hello.c -o hello.o

add.o:add.c
        gcc -c add.c -o add.o

sub.o:sub.c
        gcc -c sub.c -o sub.o

div1.o:div1.c
        gcc -c div1.c -o div1.o

hello:$(obj)
        gcc $(obj) -o hello

每次要删除.o文件,很烦,于是改写makefile如下,加了clean部分。

 rm前面的-,代表出错依然执行。比如,待删除文件集合是5个,已经手动删除了1个,就只剩下4个,然而删除命令里面还是5个的集合,就会有删除不存在文件的问题,不加这-,就会报错,告诉你有一个文件找不到。加了-就不会因为这个报错。新版不加也不报错

于是,重新make,再执行clean:

-n是先看一下执行的语句,不会删除

可以看到模拟执行后,会删除哪些文件。

确定没有问题,执行

 第三个版本的makefile

src = $(wildcard *.c)                    #add.c sub.c div1.c hello.c

obj = $(patsubst %.c, %.o,  $(src))      #把参数3中包含 参数1 的替换成 参数2  add.o sub.o div1.o hello.o



ALL:hello
hello.o:hello.c
        gcc -c hello.c -o hello.o

add.o:add.c
        gcc -c add.c -o add.o

sub.o:sub.c
        gcc -c sub.c -o sub.o

div1.o:div1.c
        gcc -c div1.c -o div1.o

hello:$(obj)
        gcc $(obj) -o hello

clean:
        -rm -rf $(obj) hello

继续升级makefile

3个自动变量

$@ :在规则命令中,表示规则的中的目标【命令的位置,目标和依赖条件不能出现】

$< :在规则命令中,表示规则中的第一个条件,如果将该变量用在模式规则中,它可以将依赖条件列表中的依赖依次取出,套用模式规则

$^ :在规则命令中,表示规则中的所有条件,组成一个列表,以空格隔开,如果这个列表中有重复项,则去重

用自动变量修改makefile,如下:

sub,add这些指令中使用$<和$^都能达到效果,但是为了模式规则,所以使用的$<

执行make,如下:

 第四个版本的makefile

src = $(wildcard *.c)                    #add.c sub.c div1.c hello.c

obj = $(patsubst %.c, %.o,  $(src))      #把参数3中包含 参数1 的替换成 参数2  add.o sub.o div1.o hello.o



ALL:hello
hello.o:hello.c
        gcc -c $< -o $@

add.o:add.c
        gcc -c $< -o $@

sub.o:sub.c
        gcc -c $< -o $@

div1.o:div1.c
        gcc -c $< -o $@

hello:$(obj)
        gcc $^ -o $@

clean:
        -rm -rf $(obj) hello

再来,上面这个makefile,可扩展性不行。比如,要添加一个乘法函数,就需要在makefile里面增加乘法函数的部分。不灵活,所以,模式规则就来了

%.o:%.c
    gcc -c $< -o $@

修改makefile,如下:

执行make,如下:

这时,增加一个mul函数,并添加mul.c文件如下:

 mul.c如下:

直接执行make:

增加函数的时候,不用改makefile,只需要增加.c文件,改一下源码,就行。很强势。

第五个版本的makefile

src = $(wildcard *.c)                    #add.c sub.c div1.c hello.c

obj = $(patsubst %.c, %.o,  $(src))      #把参数3中包含 参数1 的替换成 参数2  add.o sub.o div1.o hello.o



ALL:hello

%.o:%.c
        gcc -c $< -o $@

hello:$(obj)
        gcc $^ -o $@

clean:
        -rm -rf $(obj) hello

继续优化makefile,使用静态模式规则,就是指定模式规则给谁用,这里指定模式规则给obj用,以后文件多了,文件集合会有很多个,就需要指定哪个文件集合用什么规则

$(obj):%.o:%.c
    gcc -c $< -o $@

修改后makefile如下:

 运行如下:

 再来一个扩展

当前文件夹下有ALL文件或者clean文件时,会导致makefile瘫痪,如下所示,make clean没有工作

用伪目标来解决,添加一行   .PHONY: clean ALL

makefile如下所示:

 再来执行make clean,就不会受到干扰了

 还有一个扩展,编译时的参数,-g,-Wall这些,可以放在makefile里面,修改后makefile如下:

 执行makefile,如下:

可以直接gdb调试。

最终版本

src = $(wildcard *.c)

obj = $(patsubst %.c, %.o,  $(src))

myArgs = -Wall -g

ALL:hello

$(obj):%.o:%.c
        gcc -c $< -o $@ $(myArgs)

hello:$(obj)
        gcc $^ -o $@ $(myArgs)

clean:
        -rm -rf $(obj) hello
.PHONY:clean ALL

对比第一个版本和最终版,基本看不出来相同点

  • 作者:StudyWinter
  • 原文链接:https://blog.csdn.net/Zhouzi_heng/article/details/120235296
    更新时间:2022-07-13 12:49:45