make 和 makefile 的使用 ###通俗易懂

1. make 工具


人们通常利用 make 工具来自动完成编译工作。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。利用这种自动编译可大大简化开发工作,避免不必要的重新编译。

make 工具通过一个称为 makefile 的文件来完成并自动维护编译工作。makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要重新编译所有依赖该文件的源文件。

make 是如何工作的?

1、make 会在当前目录下找名字叫"Makefile"或"makefile";

2、如果找到,它会找文件中的第一个目标文件(target),并把这个文件作为最终的目标文件;

3、如果 main 文件不存在或是 main 所依赖的后面的 .o 文件的文件修改时间要比 main 这个文件新,那么他就会执行后面所定义的命令来生成 main 这个文件;

4、如果 main 所依赖的 .o 文件也存在,那么 make 会在当前文件中找目标为 .o 文件的依赖性,如果找到则再根据那一个规则生成 .o 文件;

5、当然,你的 C 文件和 H 文件是存在的啦,于是 make 会生成 .o 文件,然后再用 .o 文件 make 的终极任务,也就是执行文件 main 了。

注:如果 DEPENDENCIES 中有一个或多个文件更新的话,COMMAND 就要执行,这就是Makefile 最核心的内容。

2. makefile 基本规则


TARGET ... : DEPENDENCIES ...
    COMMAND
    ...
  • 目标(TARGET)程序产生的文件,如可执行文件和目标文件;目标也可以是要执行的动作,如”clean“。
  • 依赖(DEPENDENCIES)是用来产生目标的输入文件,一个目标通常依赖于多个文件。
  • 命令(COMMAND)是 make 执行的动作,一个可以有多个命令,每个占一行。注意:每个命令行的起始字符必须为 TAB 字符!

如果 DEPENDENCIES 中有一个或多个文件更新的话,COMMAND 就要执行,这就是 Makefile 最核心的内容。

最简单的 makefile 例子

main:第一目标文件,且是可执行目标(即最终的目标文件)

main:main.o add.o subtract.o
    gcc main.o add.o subtract.o -o main
main.o:main.c add.h subtract.h
    gcc -c main.c -o main.o
add.o:add.c add.h
    gcc -c add.c -o add.o
subtract.o:subtract.c subtract.h
    gcc -c subtract.c -o subtract.o

.PHONY:clean
clean:
    rm -f main.o add.o subtract.o main

常见伪目标 .PHONY

在 Makefile 中,伪目标是一种特殊的目标,它们不代表真正的文件依赖关系,而是用于执行一系列命令或其他操作。

以下是一些常见的伪目标的示例:

1、clean:用于清理生成的文件或目录。通常用于删除编译生成的目标文件、可执行文件或其他临时文件。

示例:

.PHONY: clean        //使用前进行伪目标声明,以确保它们不会与文件名冲突(标准用法)
clean:
    rm -rf target_dir/*.o
    rm -f executable

执行:

make clean

2、all:用于构建项目的所有目标或执行一系列操作。通常用于编译整个项目或执行一组任务。

示例:

all: target1 target2 target3

target1:
    # 命令和规则用于构建 target1

target2:
    # 命令和规则用于构建 target2

target3:
    # 命令和规则用于构建 target3

执行:

make all

3、install:用于安装软件或将文件复制到指定位置。通常用于将生成的可执行文件、库文件或其他资源安装到系统目录或指定位置。

示例:

install: main
    cp main /usr/local/bin/

其中 main 是可之心文件。

执行:

make install

4、test:用于运行测试套件或执行单元测试。通常用于自动化运行测试并生成测试报告。

示例:

test:
    ./run_tests.sh

执行:

make test

5、help:用于显示 Makefile 的帮助信息或目标列表。通常用于提供关于可用目标和其用途的简要说明。

示例:

help:
    @echo "Available targets:"
    @echo "  target1 - Build target1"
    @echo "  target2 - Build target2"
    @echo "  clean   - Clean up generated files"

执行:

make help

这些只是一些常见的伪目标示例,你可以根据项目的需求和特定的操作添加自定义的伪目标。在 Makefile 中,伪目标以 .PHONY 声明,以确保它们不会与文件名冲突。例如:

.PHONY: clean all install test help

通过使用伪目标,你可以更好地组织和管理 Makefile 中的任务和操作。

makefile 自动化变量

选项名

作用

$@

规则的目标文件名

$

规则的第一个依赖文件名

$^

规则的所有依赖文件列表

示例:

main:main.o add.o subtract.o
    gcc $^ subtract.o -o $@
main.o:main.c add.h subtract.h
    gcc -c $< -o $@
add.o:add.c add.h
    gcc -c $< -o $@
subtract.o:subtract.c subtract.h
    gcc -c $< -o $@

.PHONY:clean
clean:
    rm -f main main.o add.o subtract.o

makefile 中自定义变量

示例:

object=main.o add.o subtract.o 为自定义变量,变量在使用时需要 $()

object=main.o add.o subtract.o
main:$(object)
    gcc $^ subtract.o -o $@
main.o:main.c add.h subtract.h
    gcc -c $< -o $@
add.o:add.c add.h
    gcc -c $< -o $@
subtract.o:subtract.c subtract.h
    gcc -c $< -o $@

.PHONY:clean
clean:
    rm -f $(object) main

3. make 自动推导


GNU 的 make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个 [.o] 文件后都写上类似的命令,因为,我们的 make 会自动识别,并自己推导命令。

只要 make 看到一个 [.o] 文件,它就会自动的把 [.c] 文件加在依赖关系中,如果 make 找到一个 whatever.o,那么whatever.c,就会是 whatever.o 的依赖文件。并且 gcc -c whatever.c 也会被推导出来。

示例:

去掉 gcc 命令

object=main.o add.o subtract.o
main:$(object)
    gcc $^ subtract.o -o $@
main.o:add.h subtract.h
add.o:add.h
subtract.o:subtract.h

.PHONY:clean
clean:
    rm -f $(object) main

进一步简化:

去掉 .o 中间文件

object=main.o add.o subtract.o
main:$(object)
    gcc $^ subtract.o -o $@
$(object):

.PHONY:clean
clean:
    rm -f $(object) main

进一步简化:

ELF=main 为自定义变量,变量在使用时需要 $()

ELF=main
object=main.o add.o subtract.o
$(ELF):$(object)
    gcc $^ subtract.o -o $@
$(object):

.PHONY:clean
clean:
    rm -f $(object) $(ELF)

一旦新增加源文件就要修改 objects=main.o add.o subtract.o

问:有没一劳永逸的方法?不需要修改 makefile 就能适应呢?

答:借助 makefile 中的相关函数。

4. makefile 常见函数


1、wildcard 函数:当前目录下匹配模式的文件

例如:src=$(wildcard *.c)

2、notdir 函数:去除路径

例如:$(notdir $src)

3、patsubst 函数:模式匹配替换

例如:$(patsubst%.c,%.o,$src) 
等价于:$(src:.c=.o)

4、shell 函数:执行 shell 命令

例如:$(shell ls -d */)

ls -d */ 命令是获取当前目录下的文件夹,示例如下:

[root@vm10-0-0-236 make_test]# ls -d */
add/  subtract/

进一步简化:

使用 makefile 常见函数化简

ELF=main
src=$(wildcard *.c)
object=$(src:.c=.o)
$(ELF):$(object)
    echo $(src)        #打印变量结果
    echo $(object)     #打印变量结果
    gcc $^ subtract.o -o $@
$(object):

.PHONY:clean
clean:
    rm -f $(object) $(ELF)

使用 shell 命令

使用 shell 命令 find 寻找 .c 文件

ELF=main
src=$(shell find *.c)
object=$(src:.c=.o)
$(ELF):$(object)
    gcc $^ subtract.o -o $@
$(object):

.PHONY:clean
clean:
    rm -f $(object) $(ELF)

使用 shell 命令

使用 shell 命令 ls 寻找 .c 文件

ELF=main
src=$(shell ls *.c)
object=$(src:.c=.o)
$(ELF):$(object)
    gcc $^ subtract.o -o $@
$(object):

.PHONY:clean
clean:
    rm -f $(object) $(ELF)

5. 多级目录编译方法


如果源文件都在同一级目录下,使用上述 makefile 没有任何问题。

源文件在同一级目录下分布情况:

[root@vm10-0-0-236 make_test]# tree
.
├── add.c
├── add.h
├── main.c
├── makefile
├── subtract.c
└── subtract.h

但如果源文件分散在不同的目录下,上述的 makefile 便不再适用了。

源文件在不同目录下分布情况:

[root@vm10-0-0-236 make_test]# tree
.
├── add
│   ├── add.c
│   └── add.h
├── main.c
├── makefile
└── subtract
    ├── subtract.c
    └── subtract.h

多级目录 makefile 示例:

ELF=main
src=$(shell find -name '*.c')
object=$(src:.c=.o)
$(ELF):$(object)
    gcc $^ subtract.o -o $@
$(object):

.PHONY:clean
clean:
    rm -f $(object) $(ELF)

如果有用到共享库,则可以在命令后面加 -llist

示例:

gcc $^ subtract.o -o $@ -llist

事实上 gcc $^ subtract.o -o $@ -llist 也可以省略,如下:

ELF=main
CC=gcc -g -llist
src=$(shell find -name '*.c')
object=$(src:.c=.o)
$(ELF):$(object)
$(object):

.PHONY:clean
clean:
    rm -f $(object) $(ELF)

其中 CC=gcc -g -llist

  • -g :是指增加调试功能;
  • -llist :增加共享库;

附录


makefile 中 “:=” 和 “=” 的区别?

在 Makefile 中,“:=” 和 “=” 是两种不同的赋值符号,它们在变量的展开和引用上有所不同。

(1)“:=” 赋值符号(简单赋值):

  • 使用 “:=” 进行赋值时,变量的值会在赋值时立即展开,并且后续对变量的引用都会使用该展开后的值。
  • 该赋值符号只会展开一次,即使后续对变量的赋值发生变化,之前已经展开的值也不会受到影响。

(2)“=” 赋值符号(递归赋值):

  • 使用 “=” 进行赋值时,变量的值不会立即展开,而是在引用变量时才会进行展开。
  • 该赋值符号会进行递归展开,即每次引用变量时都会重新展开,以反映最新的赋值结果。

下面是一个示例,说明两者之间的区别:

# 使用 ":=" 进行赋值
VAR1 := $(shell echo "Hello, world!")
VAR2 := $(VAR1)

# 使用 "=" 进行赋值
VAR3 = $(shell echo "Hello, world!")
VAR4 = $(VAR3)

# 修改变量的值
VAR1 := $(shell echo "Goodbye!")
VAR3 = $(shell echo "Goodbye!")

# 输出变量的值
all:
    @echo "VAR1: $(VAR1)"  # 输出 "VAR1: Goodbye!"
    @echo "VAR2: $(VAR2)"  # 输出 "VAR2: Hello, world!"
    @echo "VAR3: $(VAR3)"  # 输出 "VAR3: Goodbye!"
    @echo "VAR4: $(VAR4)"  # 输出 "VAR4: Goodbye!"

在上述示例中,VAR1 和 VAR3 使用 “:=” 和 “=” 进行赋值,分别展开为 “Hello, world!” 和 “Goodbye!”。然后,VAR2 和 VAR4 分别引用 VAR1 和 VAR3 的值。

当修改 VAR1 和 VAR3 的值后,VAR1 的展开值由 “Hello, world!” 变为 “Goodbye!”,而 VAR3 的展开值仍然是 “Goodbye!”。由于 VAR2 使用的是 “:=” 赋值符号,它的展开值不会受到 VAR1 值的变化的影响,仍然是 “Hello, world!”。而 VAR4 使用的是 “=” 赋值符号,它的展开值会随着 VAR3 值的变化而更新为 “Goodbye!”。

如果需要本文 WORD、PDF 相关文档请在评论区留言!!! 

如果需要本文 WORD、PDF 相关文档请在评论区留言!!! 

如果需要本文 WORD、PDF 相关文档请在评论区留言!!! 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

数据库内核

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

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

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

打赏作者

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

抵扣说明:

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

余额充值