makefile 编写规则

1.概念

1.1 什么是makefile

Makefile 是一种文本文件,用于描述软件项目的构建规则和依赖关系,通常用于自动化软件构建过程。它包含了一系列规则和指令,告诉构建系统如何编译和链接源代码文件以生成最终的可执行文件、库文件或者其他目标文件。

1.2 使用makefile的优点

Makefile 主要用于管理大型项目的构建过程,它可以:

  1. 自动检测源代码文件的变化,只编译发生变化的文件,以提高构建效率。
  2. 根据依赖关系自动构建所需的目标文件,无需手动管理编译顺序。
  3. 支持通过简单的命令来进行构建、清理和其他任务,提高了项目的可维护性和可重复性。
  4. 可以轻松地添加自定义的构建规则和操作,满足特定项目的需求。

Makefile 使用一种称为 Make 的构建工具来解析和执行其中的规则。Make 工具会根据 Makefile 中定义的规则和依赖关系,确定需要重新构建的目标文件,并执行相应的命令来完成构建过程。

2.演变的过程

2.1 gcc编译

在我们平常linux下编译文件的时候通过gcc全部编译链接生成可执行的文件

这样编译导致的后果是每次改动某一个代码,其他文件都要跟着编译,非常的繁琐每次,那我们有什么更简单的方法吗,有

2.2 gcc单个文件编译后链接

很明显,大家已经看到了,虽然有一点优化,但不多。这也是不可取的。这时候在linux编译链接的时候就出现了makefile文件。

2.3 Makefile的工作原理

Makefile 的工作原理主要涉及两个部分:规则(rules)和依赖关系(dependencies)。Makefile 中包含了一系列规则,每个规则描述了如何生成一个或多个目标文件,并指定了生成目标文件所需的依赖文件和生成命令。Make 工具会根据这些规则来自动执行构建过程。
下面是 Makefile 的工作原理的详细解释:

1.目标和依赖关系定义:

  • Makefile 中的每个规则由一个或多个目标(targets)和其对应的依赖关系(dependencies)组成。目标通常是文件名,代表生成的目标文件或执行的操作。依赖关系是目标文件所依赖的文件列表,通常是源文件或其他目标文件。
  • 每个规则的格式通常为
 target: dependencies
     command
  • target 是目标文件或操作的名称,dependencies 是生成 target 所需的依赖文件列表,command 是生成 target 的命令。
      检查文件时间戳
    • 在执行 Makefile 时,Make 工具会检查每个目标文件和其依赖文件的时间戳,以确定哪些文件需要重新生成。
    • 如果目标文件不存在,或者其时间戳早于任何一个依赖文件的时间戳,那么 Make 工具会执行生成目标文件的命令。
    • 执行生成命令

      • 当确定需要重新生成目标文件时,Make 工具会执行该目标的生成命令。生成命令通常是编译源文件、链接目标文件或执行其他操作的命令。
      • Make 工具会按照 Makefile 中规定的顺序依次执行每个目标的生成命令,直到所有目标文件都被成功生成。
    • 递归处理依赖

      • 如果一个目标文件的依赖文件也是其他目标文件,则 Make 工具会递归处理这些依赖关系,确保所有依赖文件都被生成。
    • 增量构建

      • Make 工具会根据文件的时间戳判断哪些文件需要重新生成,从而实现增量构建。只有发生了变化的文件及其依赖文件才会重新生成,提高了构建效率。
    • 通过这样的方式,Makefile 提供了一种简洁而有效的方法来管理项目的构建过程,自动化了源代码的编译、链接和其他构建操作,使得项目的开发和维护更加高效和可靠。

      2.4 初阶Makefile文件

    • 这个版本的Makefile虽然有了一定的用途,但还是无法解决文件太多的问题。如何解决这一难题呢,我们接着向下看
    • 2.5 中阶Makefile文件


    • 虽然这个看起来已经比较完善了,但是依旧在文件太多的时候,会出现很多编译.c的文件,那有没有什么办法,让其不用这么麻烦呢
    • 2.6 后阶Makefile文件

    • CXX = gcc
      TARGET = aio
      OBJ = BintrayTree.o Queue.o test.o
      
      CXXFLAGS = -c -Wall
      
      $(TARGET) : $(OBJ)
      # $@ 表示目标文件
      # $^ 所依赖文件
      	$(CXX) -o $@ $^
      # % 是一个通配符,用于匹配任意长度的字符序列
      %.o : %.c
      # $< 第一个依赖文件
      	$(CXX) $(CXXFLAGS) $< -o $@
      
      # .PHONY防止出现于clear重名的文件而发生歧义
      .PHONY: clear
      # make clear 执行下面的命令(删除后缀.o和编译链接后的目标文件)
      clear:
      	rm -r *.o $(TARGET)

    • 这样我们,其实已经足够优化了,但是我们任然有可优化的地方,比如,我们可不可不列出链接的依赖文件,当然可以的

    • 2.7 终极版本

    • 到这里,我们就可以很清楚的认识到Makefile的优化过程
      CXX = gcc
      TARGET = aio
      #将后缀为.c的文件放入windcard中
      SRC = $(wildcard *.c)
      #路径替换,将SRC中的.c替换为.o
      OBJ = $(patsubst %.c, %.o, $(SRC))
      
      
      CXXFLAGS = -c -Wall
      
      $(TARGET) : $(OBJ)
      # $@ 表示目标文件
      # $^ 所依赖文件
      	$(CXX) -o $@ $^
      # % 是一个通配符,用于匹配任意长度的字符序列
      %.o : %.c
      # $< 第一个依赖文件
      	$(CXX) $(CXXFLAGS) $< -o $@
      
      # .PHONY防止出现于clear重名的文件而发生歧义
      .PHONY: clear
      # make clear 执行下面的命令(删除后缀.o和编译链接后的目标文件)
      clear:
      	rm -r *.o $(TARGET)


      3,Makefile的编码规则

    • 我们之前已经有所了解Makefile的编码规则,但是在这里我还是觉得有必要总体讲一下

      在编写 Makefile 时,遵循一些编码规则可以使其更加清晰、易于维护和跨平台。下面是一些常见的 Makefile 编码规则:

      1.缩进使用Tab键:
      Makefile 中命令行必须使用 Tab 键进行缩进,而不是空格。这是因为 Make 工具默认使用 Tab 键作为命令行的缩进标识,使用空格可能会导致 Make 解析错误。
      2.目标和依赖关系之间的冒号:
      目标(target)和依赖关系(dependencies)之间使用冒号(:)分隔。冒号前面是目标,后面是依赖关系。
      3.命令行前面的Tab键:
      在每个规则中,命令行必须以 Tab 键开头,表示该命令是该规则的执行命令。除了注释以外,任何其他以 Tab 键开头的行都被视为命令。
      4.变量使用大写字母:
      为了与命令和目标区分开,通常将变量使用大写字母命名。例如:CC = gcc。
      5.使用变量代替硬编码的命令和路径:
      使用变量来代替硬编码的命令和路径,使得 Makefile 更加灵活和可移植。例如,将编译器命令使用变量表示:CC = gcc,然后在规则中使用 $(CC) 来引用。

    • 6.使用伪目标:
      对于不产生实际文件的操作(如清理、安装等),使用伪目标(.PHONY)来定义。这样可以确保即使存在同名文件,也不会误导 Make 工具。例如:.PHONY: clean。
      7.注释使用 #:
      使用 # 符号来添加注释,使得 Makefile 更具可读性。注释可以帮助理解 Makefile 中每个规则的作用。
      8.模块化设计:
      将 Makefile 模块化,分成多个小的 Makefile 文件,然后通过 include 指令引入。这样可以提高 Makefile 的可维护性和可读性,减少重复代码。
      9.合理使用条件语句:
      可以使用条件语句(如 ifeq、ifdef 等)来根据不同的条件执行不同的规则或命令,以实现更灵活的构建过程。
      10.跨平台兼容性:
      考虑到不同平台下的路径分隔符和命令格式的差异,编写具有跨平台兼容性的 Makefile。可以使用自动化工具或条件语句来处理不同平台下的差异。

      遵循这些规则可以使 Makefile 更加规范、易读和易于维护,有助于提高项目的构建效率和可靠性。

      4,每期一问

    • 4.1 上期答案

      // 计算树的高度
      int getHeight(struct TreeNode* root) {
          if (root == NULL) {
              return 0;
          }
          int leftHeight = getHeight(root->left);
          int rightHeight = getHeight(root->right);
          return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
      }
      
      // 判断是否是平衡二叉树的辅助函数
      bool isBalancedHelper(struct TreeNode* root) {
          if (root == NULL) {
              return true;
          }
          int leftHeight = getHeight(root->left);
          int rightHeight = getHeight(root->right);
          if (abs(leftHeight - rightHeight) <= 1 && isBalancedHelper(root->left) && isBalancedHelper(root->right)) {
              return true;
          }
          return false;
      }
      
      // 判断是否是平衡二叉树
      bool isBalanced(struct TreeNode* root) {
          return isBalancedHelper(root);
      }

      4.2 本期问题. - 力扣(LeetCode)

  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值