Makefile(7)------Makefile工程实战

本文详细介绍了如何使用Makefile构建和管理大型工程,包括目录管理、头文件依赖、静态库和动态库的生成、第三方库的使用以及Makefile的重构。文章通过一个计算器项目,演示了如何逐步优化Makefile,实现源文件、目标文件、依赖文件的自动化管理和编译,同时讲解了如何处理头文件的依赖关系,以及如何在不同目录下管理源代码和目标文件,以提高工程的可维护性和可扩展性。
摘要由CSDN通过智能技术生成

插播!插播!插播!亲爱的朋友们,我们的Makefile课程上线啦!感兴趣的小伙伴可以去下面的链接学习哦~

https://edu.csdn.net/course/detail/39264

1. 构建工程

通过前面知识点对Makefile的语法还有框架做出了简要介绍后,通过实战一步一步写一个简单的计算器项目。实现通过makefile来管理编译代码,包括生成静态库和动态库,多目录管理文件等等一系列架构组织,完成一个通用的Makefile模板。

首先先来构建一个加减乘除的一个简单的计算器项目工程。新建一个目录叫operation,在此新建目录下创建add.c add.h operation.c为加法模块,运算功能。 定义一个计算器计算的主文件operation.c,在这个主文件中会调用加法模块。

代码如下:

add.c

#include<stdio.h>

void add_init()
{
    printf("add operation init\n");
}

add.h

#ifndef _ADD_H
#define _ADD_H

void add init();

#endif

operation.c

#include<stdio.h>
#include "add.h"
#include "sub.h"

int main()
{
    printf("operation beginning\n");
    add_init();
    sub_init();
    return 0;
}

那么,makefile该怎么写呢,可以这样写:

Makefile:

PHONY:al clean

TARGET = test
0BJ = add.o operation.o

all:$(TARGET)

$(TARGET):$(OBJ)
    gcc -o s@ $^

%.o:%.c
    gcc -o s@ -c S^

clean:
    rm -rf $(OBJ) S(TARGET)

TARGET:计算器应用程序的可执行文件test

OBJ:包含需要生成以创建可执行文件的所有目标文件的名称。

%.c: 表示任何以 .c 为后缀的源文件。

%.o: 表示任何以 .o 为后缀的目标文件。

$@$^是我们之前讲过的自动变量,$@表示目标,$^表示所有目标依赖

clean:伪目标用于删除所有生成的目标文件和可执行文件。

%.o:%.c
    gcc -o s@ -c S^

这个规则是为了生成从源文件到目标文件的编译规则。

先定义了一个伪目标all,然后让all去依赖可执行程序test。即使没有all,我也能通过make直接生成test. 为什么要多此一举呢?之前在讲依赖树的时候我们注意到,树顶一般只有一个,但是如果我想直接make生成多个可执行程序时比如test1,test2,如果没有一个all作为树顶。我就无法直接make生成两个程序,而是只能make test1, make test2,这其实非常破坏依赖树的结构的,所以我们约定伪目标all是第一个目标,它通常依赖于构建整个项目所需的所有子目标,比如对于上面提到的test1,test2,我就让它们依赖于all,这样我直接make就能用时构建出这两个程序。

当执行 "test" 目标时,它首先检查目标文件是否存在。如果目标文件不存在,它使用 Makefile 中定义的规则编译相应的源文件。一旦所有目标文件生成,目标将它们链接起来创建可执行文件 "test"。

当我们再添加减法模块时,将operation.c修改:

sub.c

#include<stdio.h>

void sub init()
{
    printf("sub operation init\n");
}

sub.h

#ifndef _SUB_H
#define _SUB_H

void sub_init();

#endif

operation.c

#include<stdio.h>
#include "add.h"
#include "sub.h"

int main()

{
    printf("operation beginning\n");
    add_init();
    sub_init();
    return 0;
}

此时,makefile只需要添加sub.o目标文件在变量OBJ上即可,添加好之后,我们直接make,可以看见sub.o已经生成了。运行一下test,我们也可以看见sub减法运算模块也已经正常使用了。

Makefile:

PHONY:all clean

TARGET = test
oBJ = add.o sub.o operation.o

all:$(TARGET)

$(TARGET):$(OBJ)
    gcc -o $@ $^

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

clean:
    rm -rf $(OBJ) $(TARGET)

当继续添加其他模块的时候,是否直接再在OBJ变量上添加目标文件,那如果一个工程里面有几百个甚至更多.c和.h文件时,这样写就非常麻烦,此时为了使makefile更富有通用性,更加简便,因此我们可以使用前面课程中makefile函数中的讲解过的一个函数叫做获取匹配模式文件名函数----wildcard,这个函数在这里可以发挥特别的作用。

因此我们可以通过这个函数升级前面写的makefile ,定义一个变量:

 SRCS = $(wildcard *c)

 SRCS就是当前目录下所有的.c文件

OBJ = $(SRCS:.c=.o)

把所有的.c替换成.o

则OBJ就变成了当前目录下的所有.o文件,此时,OBJ变量的含义其实跟上述内容所实现的含义一样。这样优化后,不管添加多少个.c文件,这个makefile都是适用的,不需要进行修改。

Makefile如下:

PHONY:all clean

TARGET = test
SRCS = $(wildcard *.c)
0BJ = $(SRCS:.c=.o)

all:$(TARGET)

$(TARGET):$(OBJ)
    gcc -o $@ $^

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

clean:
    rm -rf $(OBJ) $(TARGET)

可见此时执行结果是一样的,在用函数设置变量时,我们也可以打印一下看看,这两个变量是否跟我们预想的一样,只需要添加打印语句:

@echo "SRCS = $(SRCS)

@echo "OBJ = $(OBJ)

2.自动添加目标对头文件的依赖

以上规则中,每个.o文件只依赖对应的.c文件,并没有依赖对应的.h文件,那会造成什么样的现象呢?

我们举个例子说说:

修改sub.h头文件,宏定义一个变量math为8,例如:

sub.h

#ifndef _SUB_H
#define _SUB_H

void sub_init();

#define math 8

#endif

在sub.c的函数中打印math的值,编辑sub.c,包含sub.h ,增加打印语句

sub.c

#include<stdio.h>
#include "sub.h"

void sub init()
{
    printf("sub operation init\n");
    printf("number=%d\n",math);
}

修改完后,make执行一下,此时math=8,这个时候改一下这个宏定义的值,将math改为9,然后再进行编译make,出现如下

make: Nothing to be done for all'

说明执行make命令时,所有需要的文件已经是最新时就会出现这个消息,这意味着刚才修改了sub.h文件并没有成功重新编译。

可以编译可执行程序test查看:这时发现了,明明已经修改了将math的值改为9,此时math怎么依然值为8。

这是因为在上面的规则中,每个.o文件只依赖了对应的.c文件,并没有依赖对应的.h文件。 所以当.h文件修改了之后,不会再去重新编译所有依赖该.h文件的目标文件,需要在Makefile中添加目标对应.h文件的依赖关系!

因此我们就需要添加对头文件的依赖,此时sub.o就得将它设置为依赖于sub.h和sub.c,为以下格式:

sub.o:sub.c sub.h

这个规则可不用写生成命令,因为前面讲过多规则目标,当多个规则是同一个目标时,会将目标依赖进行合并成此目标的依赖文件列表,并且只使用最后一个规则中定义的命令即可。 则此时其实内部是这样的:

sub.o:sub.c usb.h
    gcc -o sub.o -c usb.c usb.h

修改后如下:

.PHONY:all clean

TARGET = test
SRCS = $(wildcard *.c)
0BJ = $(SRCS:.c=.o)

all:$(TARGET)

$(TARGET):$(OBJ)
    gcc -o $@ $^

sub.o:sub.c sub.h

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

clean:
    rm -rf $(OBJ) $(TARGET)

加入sub.h的依赖关系后执行make

gcc: fatal error: cannotspecify -o with s or -E with multiple files compilation terminated.

可见执行的命令与预想一致,但是又出现这个错误是怎么回事 ?错误显示makefile的语法错误,使用选项-c时不能与多个文件一起使用,我们得将.c文件过滤出来,这时我们可以使用filter过滤函数,让其更加简便。

此时如果我们要保留下.c文件,可以这样写

$(filter %.c,$^)

编译可执行程序如下:此时math已经为9,可以修改再进行实验!

其实,在讲基础部分讲过,在makefile中,可以自动生成头文件依赖关系,使用gcc -MM xx.c 命令会自动生成此.c文件头文件依赖关系:

gcc -MM sub.c>sub.d

并将依赖关系重定位到.d文件中,执行命令语句为 : gcc -MM XX.c>XX.d ,则.d文件中就是其依赖关系。

生成sub.d文件后,于是我们可以将makefile再次改写一下:

.PHONY:all clean

TARGET = test
SRCS = $(wildcard *.c)
0BJ = $(SRCS:.c=.o)

all:$(TARGET)

$(TARGET):$(OBJ)
    gcc -o $@ $^

include sub.d

%.o:%.c
    gcc -o $@ -c $(filter %.c,$^)

clean:
    rm -rf $(OBJ) $(TARGET)

同样我们也能达到前面的效果,但是一个一个手动生成.d然后include太麻烦了 ,可以再次修改makefile,实现用makefile就可以直接生成每个.c的.d文件。 添加一条规则:

%d:%c
    gcc -MM $^>$@

然后make执行一下发现不能生成.d文件,这是为什么呢?这是因为我们呢make执行的实际上是make all,但是.d文件并没有在all的依赖树中。所以我们要生成.d文件就需要让all依赖于.d文件。

因此我们可以定义一个变量DEP,将当前目录下的.c文件都转换为.d文件。然后让all去依赖于这个变量。如果直接include,.d文件可能还没有创建,就会有一个警告。

所以我们可以增加一个判断语句如果.d文件存在,再将其包含进来: 别忘了在clean下面要加上DEP。

.PHONY:all clean

TARGET = test
SRCS = $(wildcard *.c)
0BJ = $(SRCS:.c=.o)
DEP = $(SRCS:.c=.d)

all:$(TARGET) $(DEP)

ifneq ("$(wildcard $(DEP))","")
include $(DEP)
endif

$(TARGET):$(OBJ)
    gcc -o $@ $^

%.o:%.c
    gcc -o $@ -c $(filter %.c,$^)

%d:%c
    gcc -MM $^>$@

clean:
    rm -rf $(OBJ) $(TARGET) $(DEP)

此时make一下,发现没有什么问题了。

3.目录管理源文件

将不同的模块都放入不同的目录下,这样便于我们更好的去管理我们的代码架构。这里我们可以看到我们已经有一个主程序operation.c,还有两个功能模块,分别为加法和减法模块。因此新建两个目录add和sub,将add.c和add.h放入add目录中,sub也同样如此。再新建一个目录calculator用来存放主程序operation.c文件。放好之后,我们先编译看看,例如进入到add目录下,如果想编译这个模块,我们还得需要一个makefile文件,先把上节的Makefile放到add目录下:

所以此时make发现会有错误。makefile需要修改一下,在add这个目录下没有main函数,所以要把TARGET的定义去掉。 TARGET为空的话下面的关于它的规则就不会生效,那.o文件就不会在依赖关系树里面,所以我们要在all后面加上OBJ,同时要调整一下位置,把OBJ和DEP放到前面,因为先生成依赖文件再生成依赖目标才是合理的。

.PHONY:all clean

TARGET =
SRCS = $(wildcard *.c)
0BJ = $(SRCS:.c&#
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值