在项目中为什么要使用Makefile

文章目录

1. Makefile

2. Makefile和GCC对比

示例代码

GCC

Makefile

3. Makefile的规则

3.1 命名规则

3.2 目标生成规则

3.3 目标更新

4. 代码示例


1. Makefile

项目复杂性带来的问题

在大型项目中,代码通常被组织成多个文件和模块,每个模块可能包含许多源文件、头文件、库文件等。直接在终端输入所有的 GCC 命令行来编译整个项目显然是不现实的。手动管理这些命令既费时又容易出错,特别是在以下情况下:

  • 文件数量庞大:需要编译的文件数目非常多,手动输入命令变得繁琐。
  • 模块化设计:项目被拆分成多个模块,每个模块可能由多个文件组成。
  • 频繁变动:代码在开发过程中频繁修改和更新,需要重新编译部分或全部代码。

Makefile 的必要性

为了解决上述问题,我们需要一个工具来自动化和管理这些编译过程,这就是 make。Makefile 是 make 的配置文件,定义了如何编译和链接程序的规则和依赖关系。通过 Makefile,我们可以实现以下目的:

  1. 自动化编译:定义好规则后,只需运行 make 命令,make 工具会自动完成编译过程。
  2. 依赖管理:Makefile 可以管理文件之间的依赖关系,只在必要时重新编译受影响的文件,避免重复工作。
  3. 提高效率:减少手动输入命令的工作量,提高开发效率和准确性。

Makefile 的规则和功能

Makefile 是由一系列规则组成的,每条规则描述了如何生成一个或多个目标文件。基本的 Makefile 规则包括:

  • 目标文件:需要生成的文件,如可执行文件或对象文件。
  • 依赖文件:目标文件所依赖的源文件或头文件。
  • 命令:生成目标文件所需执行的命令。

2. Makefile和GCC对比

示例代码

下面用一个例子来说明Makefile如何简化编译流程。

创建一个工程内容分别main.c,sub.c,sub.h,add.c,add.h五个文件。sub.c负责计算两个数减法运算,add.c负责计算两个数加法运算,然后编译出可执行文件。

main.c

这是主程序文件,包含了add.hsub.h头文件,定义了main函数,调用了addsub函数并输出结果。

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

int main(int argc, char *argv[])
{
    printf("abc, add:%d\n", add(10, 10));
    printf("abc, sub:%d\n", sub(20, 10));
    return 0;
}

add.c

这个文件定义了add函数,函数体简单地返回两个整数的和。

#include "add.h"

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

add.h

这是add函数的头文件,使用了预处理指令来避免重复包含,声明了add函数。

#ifndef __ADD_H
#define __ADD_H

int add(int a, int b);

#endif

sub.c

这个文件定义了sub函数,函数体简单地返回两个整数的差。

#include "sub.h"

int sub(int a, int b)
{
    return a - b;
}

sub.h

这是sub函数的头文件,使用了预处理指令来避免重复包含,声明了sub函数。

#ifndef __SUB_H
#define __SUB_H

int sub(int a, int b);

#endif

GCC

首先使用 gcc 命令对多个源文件进行编译和链接,生成一个可执行程序。

1. 编译所有源文件并链接生成可执行程序

gcc main.c sub.c add.c -o output
  • 该命令将 main.csub.cadd.c 文件进行编译,并将生成的目标文件链接成名为 output 的可执行文件。
  • 使用 ls 命令查看当前目录,可以看到源文件和生成的可执行文件。
  • 运行生成的可执行文件 ./output,可以看到程序输出的结果。

输出:

abc, add:20
abc, sub:10

该命令将所有源文件一起编译并链接,生成了最终的可执行文件。

2. 拆分编译和链接步骤

对于一个大型项目,仅仅修改了一个源文件时,如果重新编译所有文件将非常耗时。因此,可以使用拆分编译和链接步骤来提高效率。

  • 分别编译每个源文件,生成目标文件:

gcc -c main.c
gcc -c sub.c
gcc -c add.c
  • 将生成的目标文件链接成一个可执行文件:

gcc main.o sub.o add.o -o output

这样做的好处是,如果只修改了某个源文件,只需重新编译该文件,而无需重新编译所有文件。例如,如果我们修改了 add.c 文件,只需要重新编译 add.c

gcc -c add.c
gcc main.o sub.o add.o -o output

这种方法显然可以节省时间,但仍然存在几个问题:

  1. 如果源文件很多,手动管理编译和链接命令会非常繁琐。
  2. 每次添加新源文件或修改文件时,必须手动更新编译和链接命令。
  3. 如果头文件内容变化,手动管理依赖关系会非常复杂。

 

Makefile

为了自动化管理编译和链接过程,我们可以使用 MakefileMakefile 是一个定义了一系列规则和依赖关系的文件,用于自动化构建过程。通过 Makefile,可以简化复杂项目的编译过程,提高开发效率。

Makefile 可以自动检测文件的变化,并根据定义的规则只重新编译需要更新的文件,而不必每次都重新编译所有文件。这解决了手动管理编译命令和依赖关系的问题,使得项目构建更加高效和可靠。

编写 Makefile

output: main.o add.o sub.o
    gcc -o output main.o add.o sub.o

main.o: main.c
    gcc -c main.c

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

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

clean:
    rm *.o output

这个 Makefile 定义了以下几项规则:

目标文件 output:

  • 依赖于 main.oadd.osub.o
  • 命令行 gcc -o output main.o add.o sub.o 表示当 main.oadd.osub.o 这些目标文件发生变化时,重新链接生成 output 可执行文件。

目标文件 main.o:

  • 依赖于源文件 main.c
  • 命令行 gcc -c main.c 表示当 main.c 文件发生变化时,重新编译生成 main.o 目标文件。

目标文件 add.o:

  • 依赖于源文件 add.c
  • 命令行 gcc -c add.c 表示当 add.c 文件发生变化时,重新编译生成 add.o 目标文件。

目标文件 sub.o:

  • 依赖于源文件 sub.c
  • 命令行 gcc -c sub.c 表示当 sub.c 文件发生变化时,重新编译生成 sub.o 目标文件。

清理命令 clean:

  • 删除所有的目标文件和可执行文件。

使用 Makefile

编写好 Makefile 之后,只需要在终端中执行 make 命令,make 工具会根据 Makefile 中定义的规则自动化地管理编译过程。

  • 查看当前目录内容:

$ ls
add.c add.h main.c Makefile sub.c sub.h
  • 执行 make 命令:

$ make
gcc -c main.c
gcc -c add.c
gcc -c sub.c
gcc -o output main.o add.o sub.o
  • 查看生成的文件:

$ ls
add.c add.h main.c Makefile output sub.c sub.h main.o add.o sub.o
  • 再次执行 make 命令:

    如果没有文件发生变化,make 会显示 up to date,表示所有文件已经是最新的,不需要重新编译:

$ make
make: 'output' is up to date.
  • 修改某个源文件并重新编译:

    假设修改了 add.c 文件,只需要重新编译该文件,并将所有的目标文件链接成新的可执行文件:

$ make
gcc -c add.c
gcc -o output main.o add.o sub.o

通过这种方式,Makefile 使得编译过程自动化,避免了手动管理多个文件的复杂性,提高了开发效率。

3. Makefile的规则

3.1 命名规则

Makefile 文件的命名可以是 Makefilemakefile,都可以被 make 工具识别。尽量使用这两个标准命名,以避免配置 make 命令时的额外麻烦。不过,你也可以使用其他名字,比如 makefile.linux,此时需要使用 -f 参数指定文件名:

make -f makefile.linux

Makefile 的基本语法规则包括:

目标(target)

  • 目标是 Makefile 中需要生成的文件或执行的任务。例如,可执行文件或目标文件。

依赖(prerequisites)

  • 依赖是生成目标所需要的一些文件。如果依赖文件发生变化,make 将重新生成目标。

命令(command)

  • 命令是为了生成目标所需要执行的具体命令。这些命令通常是 shell 命令,必须以 Tab 键开头。

一个目标和依赖的基本格式如下:

target: prerequisites
[Tab]command

示例:

假设在命令行中输入以下编译命令:

gcc main.c -o main

对应的 Makefile 可以写成:

main: main.c
    gcc main.c -o main
  1. 第一行定义了目标 main 依赖于 main.c 文件。
  2. 第二行是生成 main 可执行文件所需要的命令 gcc main.c -o main。注意命令前必须有一个 Tab 键。

注意事项

  • Makefile 中的命令行必须以 Tab 键开头,不能使用空格。
  • Makefile 中的每条命令都在一个新的 shell 环境中执行,这意味着在同一规则的多行命令中,前一行的环境变化不会影响到下一行。
  • 使用 -f 参数可以指定不同的 Makefile 文件名,方便在不同环境或项目中使用。

3.2 目标生成规则

目标生成是指 Makefile 根据规则生成目标文件的过程。

检查依赖文件

  • Makefile 首先会检查目标文件所依赖的文件是否存在。

生成依赖文件

  • 如果依赖文件不存在,Makefile 将根据规则生成依赖文件。

  1. 检查依赖1:如果存在,继续检查下一个依赖。
  2. 检查依赖2:如果存在,继续检查下一个依赖。
  3. 检查依赖3:如果不存在,寻找规则生成依赖3。
  4. 编译生成目标:所有依赖都存在后,最终生成目标文件。

3.3 目标更新

目标更新是指当依赖文件发生变化时,Makefile 重新生成目标文件的过程。

检查依赖更新

  • Makefile 会检查目标文件的所有依赖文件,任何一个依赖文件有更新,都会触发目标文件的重新生成。

时间比较

  • Makefile 通过比较目标文件和依赖文件的时间戳来决定是否需要更新。如果依赖文件的时间戳比目标文件更新,则需要更新目标文件。

  1. 检查依赖更新1:如果未更新,继续检查下一个依赖。
  2. 检查依赖更新2:如果未更新,继续检查下一个依赖。
  3. 检查依赖更新3:如果更新,重新生成依赖并更新目标。

4. 代码示例

Makefile 示例

output: main.o add.o sub.o
    gcc -o output main.o add.o sub.o

main.o: main.c
    gcc -c main.c

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

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

clean:
    rm *.o output

编译执行过程

当前目录下有 main.cadd.csub.c 以及上述 Makefile 文件。现在在终端中执行 make 命令来编译这些源文件:

$ make

这个命令的输出如下:

gcc -c main.c
gcc -c add.c
gcc -c sub.c
gcc -o output main.o add.o sub.o

初次执行 make 命令:

  • make 命令会检测 output 目标的依赖 main.oadd.osub.o 是否存在。如果不存在,则会寻找生成这些依赖的命令。

  • output 依赖于 main.oadd.osub.o,这三个文件都不存在,所以 make 会首先生成它们。依赖关系如下:

    • main.o 依赖 main.c,于是使用命令 gcc -c main.c 来生成 main.o
    • add.o 依赖 add.c,于是使用命令 gcc -c add.c 来生成 add.o
    • sub.o 依赖 sub.c,于是使用命令 gcc -c sub.c 来生成 sub.o
  • 现在 main.oadd.osub.o 都已经生成,但是 output 文件还没有生成,所以 make 会继续执行 gcc -o output main.o add.o sub.o 命令来生成 output

当修改某个文件时:

比如,我们修改了 add.c 文件,然后再次执行 make 命令:

$ make

这个命令的输出如下:

gcc -c add.c
gcc -o output main.o add.o sub.o
  • make 命令会再次检查依赖关系,发现 add.c 文件比 add.o 文件更新,所以需要重新编译 add.c 文件生成 add.o

  • 其他文件 main.osub.o 没有变化,所以不需要重新编译。

  • 最后,make 会执行 gcc -o output main.o add.o sub.o 命令来生成新的可执行文件 output

通过 Makefile,我们可以轻松管理源文件的编译过程,避免手动输入复杂的编译命令。make 命令会自动检测依赖关系,只编译需要更新的文件,提高了编译效率。

  • 31
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

TENET-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值