• 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 基本规则


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

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

    最简单的 makefile 例子

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

    1. main:main.o add.o subtract.o
    2. gcc main.o add.o subtract.o -o main
    3. main.o:main.c add.h subtract.h
    4. gcc -c main.c -o main.o
    5. add.o:add.c add.h
    6. gcc -c add.c -o add.o
    7. subtract.o:subtract.c subtract.h
    8. gcc -c subtract.c -o subtract.o
    9. .PHONY:clean
    10. clean:
    11. rm -f main.o add.o subtract.o main

    常见伪目标 .PHONY

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

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

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

    示例:

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

    执行:

    make clean

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

    示例:

    1. all: target1 target2 target3
    2. target1:
    3. # 命令和规则用于构建 target1
    4. target2:
    5. # 命令和规则用于构建 target2
    6. target3:
    7. # 命令和规则用于构建 target3

    执行:

    make all

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

    示例:

    1. install: main
    2. cp main /usr/local/bin/

    其中 main 是可之心文件。

    执行:

    make install

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

    示例:

    1. test:
    2. ./run_tests.sh

    执行:

    make test

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

    示例:

    1. help:
    2. @echo "Available targets:"
    3. @echo " target1 - Build target1"
    4. @echo " target2 - Build target2"
    5. @echo " clean - Clean up generated files"

    执行:

    make help

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

    .PHONY: clean all install test help

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

    makefile 自动化变量

    选项名

    作用

    $@

    规则的目标文件名

    $

    规则的第一个依赖文件名

    $^

    规则的所有依赖文件列表

    示例:

    1. main:main.o add.o subtract.o
    2. gcc $^ subtract.o -o $@
    3. main.o:main.c add.h subtract.h
    4. gcc -c $< -o $@
    5. add.o:add.c add.h
    6. gcc -c $< -o $@
    7. subtract.o:subtract.c subtract.h
    8. gcc -c $< -o $@
    9. .PHONY:clean
    10. clean:
    11. rm -f main main.o add.o subtract.o

    makefile 中自定义变量

    示例:

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

    1. object=main.o add.o subtract.o
    2. main:$(object)
    3. gcc $^ subtract.o -o $@
    4. main.o:main.c add.h subtract.h
    5. gcc -c $< -o $@
    6. add.o:add.c add.h
    7. gcc -c $< -o $@
    8. subtract.o:subtract.c subtract.h
    9. gcc -c $< -o $@
    10. .PHONY:clean
    11. clean:
    12. 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 命令

    1. object=main.o add.o subtract.o
    2. main:$(object)
    3. gcc $^ subtract.o -o $@
    4. main.o:add.h subtract.h
    5. add.o:add.h
    6. subtract.o:subtract.h
    7. .PHONY:clean
    8. clean:
    9. rm -f $(object) main

    进一步简化:

    去掉 .o 中间文件

    1. object=main.o add.o subtract.o
    2. main:$(object)
    3. gcc $^ subtract.o -o $@
    4. $(object):
    5. .PHONY:clean
    6. clean:
    7. rm -f $(object) main

    进一步简化:

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

    1. ELF=main
    2. object=main.o add.o subtract.o
    3. $(ELF):$(object)
    4. gcc $^ subtract.o -o $@
    5. $(object):
    6. .PHONY:clean
    7. clean:
    8. 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 函数:模式匹配替换

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

    4、shell 函数:执行 shell 命令

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

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

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

    进一步简化:

    使用 makefile 常见函数化简

    1. ELF=main
    2. src=$(wildcard *.c)
    3. object=$(src:.c=.o)
    4. $(ELF):$(object)
    5. echo $(src) #打印变量结果
    6. echo $(object) #打印变量结果
    7. gcc $^ subtract.o -o $@
    8. $(object):
    9. .PHONY:clean
    10. clean:
    11. rm -f $(object) $(ELF)

    使用 shell 命令

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

    1. ELF=main
    2. src=$(shell find *.c)
    3. object=$(src:.c=.o)
    4. $(ELF):$(object)
    5. gcc $^ subtract.o -o $@
    6. $(object):
    7. .PHONY:clean
    8. clean:
    9. rm -f $(object) $(ELF)

    使用 shell 命令

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

    1. ELF=main
    2. src=$(shell ls *.c)
    3. object=$(src:.c=.o)
    4. $(ELF):$(object)
    5. gcc $^ subtract.o -o $@
    6. $(object):
    7. .PHONY:clean
    8. clean:
    9. rm -f $(object) $(ELF)

    5. 多级目录编译方法


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

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

    1. [root@vm10-0-0-236 make_test]# tree
    2. .
    3. ├── add.c
    4. ├── add.h
    5. ├── main.c
    6. ├── makefile
    7. ├── subtract.c
    8. └── subtract.h

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

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

    1. [root@vm10-0-0-236 make_test]# tree
    2. .
    3. ├── add
    4. │ ├── add.c
    5. │ └── add.h
    6. ├── main.c
    7. ├── makefile
    8. └── subtract
    9. ├── subtract.c
    10. └── subtract.h

    多级目录 makefile 示例:

    1. ELF=main
    2. src=$(shell find -name '*.c')
    3. object=$(src:.c=.o)
    4. $(ELF):$(object)
    5. gcc $^ subtract.o -o $@
    6. $(object):
    7. .PHONY:clean
    8. clean:
    9. rm -f $(object) $(ELF)

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

    示例:

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

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

    1. ELF=main
    2. CC=gcc -g -llist
    3. src=$(shell find -name '*.c')
    4. object=$(src:.c=.o)
    5. $(ELF):$(object)
    6. $(object):
    7. .PHONY:clean
    8. clean:
    9. rm -f $(object) $(ELF)

    其中 CC=gcc -g -llist

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

    附录


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

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

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

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

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

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

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

    1. # 使用 ":=" 进行赋值
    2. VAR1 := $(shell echo "Hello, world!")
    3. VAR2 := $(VAR1)
    4. # 使用 "=" 进行赋值
    5. VAR3 = $(shell echo "Hello, world!")
    6. VAR4 = $(VAR3)
    7. # 修改变量的值
    8. VAR1 := $(shell echo "Goodbye!")
    9. VAR3 = $(shell echo "Goodbye!")
    10. # 输出变量的值
    11. all:
    12. @echo "VAR1: $(VAR1)" # 输出 "VAR1: Goodbye!"
    13. @echo "VAR2: $(VAR2)" # 输出 "VAR2: Hello, world!"
    14. @echo "VAR3: $(VAR3)" # 输出 "VAR3: Goodbye!"
    15. @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 相关文档请在评论区留言!!! 

  • 相关阅读:
    【信息检索与数据挖掘期末复习】(五)Language Model
    delphi 7常用快捷键总结
    Javaweb之Vue的概述
    Java数组遍历深度解析
    南美巴西市场最全分析开发攻略,收藏一篇就够了
    【Master公式】对于这一类符合的递归可以直接确定时间复杂度
    【muduo源码剖析】Buffer类的设计
    解锁数据库运维秘籍:掌握AntDB-T动态共享内存,提升进程间通信效率
    java:SpringBoot入门
    C++之STL简介
  • 原文地址:https://blog.csdn.net/weixin_47156401/article/details/133999960