以下是一些推荐的学习Makefile的网站和资源,可以帮助你快速掌握Makefile的常用规则、结构和示例:

  1. 廖雪峰的官方网站
    廖雪峰的Makefile教程从基础概念讲起,通过简单易懂的例子逐步深入,非常适合作为入门学习。
    网址:Makefile基础 - Makefile教程 - 廖雪峰的官方网站
  2. 腾讯云技术文档
    腾讯云上有一篇关于Makefile的详细解释和示例,涵盖了从基础到进阶的内容,包括如何编写Makefile以及一些高级用法。
    网址:超清晰的makefile解释、编写与示例 - 腾讯云
  3. 博客园 - Makefile语法总结
    这篇文章总结了Makefile的常用语法和规则,并通过具体示例进行解析,适合有一定基础的读者深入学习。
    网址:Makefile语法详细总结及示例解析(快速掌握)
  4. CSDN博客 - Makefile教程
    CSDN上有许多关于Makefile的教程和示例,其中一些文章提供了详细的规则和实际项目中的Makefile模板。
    网址:makefile常用的命令总结及简单示例 - CSDN博客
  5. GitHub Pages - Makefile教程
    这是一个开源的Makefile教程,提供了从入门到高级的详细内容,并且包含了许多实用的示例。
    网址:Makefile 教程 - GitHub Pages
  6. CSDN博客 - 通用Makefile模板
    这篇文章提供了一个通用的Makefile模板,适用于C++项目的编译和链接,是一个很好的参考。
    网址:makeFile基本介绍, 语法, 示例,通用makefile - CSDN博客

Makefile 的基本概念

  • 定义:Makefile 是一个用于自动化编译和构建项目的文件,它描述了工程的编译、链接等规则,包括哪些源文件需要编译、如何编译、如何生成目标文件等。
  • 作用:通过编写 Makefile,可以使用 make 命令工具来自动化编译过程,避免手动输入繁琐的编译命令,提高开发效率。

Makefile 的基本结构

  • 变量定义:用于简化和统一文件名或命令的书写。例如:
    • CC = gcc:定义编译器为 gcc。
    • CFLAGS = -Wall -g:定义编译选项,-Wall 显示所有警告,-g 启用调试信息。
    • TARGET = my_program:定义目标文件名为 my_program
    • OBJECTS = main.o utils.o:定义目标文件列表。
  • 规则定义:指定目标文件、依赖文件和生成目标文件的命令。基本格式如下:
    • target: dependencies:目标文件和依赖文件。
    • command:生成目标文件的命令,必须以 Tab 键开始。
  • 伪目标:用于执行一些特定的操作,如清理编译生成的文件。例如:
    • .PHONY: clean:声明 clean 是一个伪目标。
    • clean::定义清理操作的命令。

Makefile 的基本语法

  • 目标和依赖:目标是要生成的文件,依赖是生成目标所需要的文件。基本格式如下:

    • target: dependencies:目标文件和依赖文件。
    • command:生成目标文件的命令,必须以 Tab 键开始。
  • 内置变量:Makefile 提供了一些内置变量,常用的包括:

    • $@:目标文件的名称。
    • $^:所有依赖文件的名称。
    • $<:第一个依赖文件的名称。

    image-20250423142456075

  • 通配符:用于匹配文件名或文件路径中的多个字符,以便在规则中批量处理文件。常见的通配符有:

    • *:匹配零个或多个字符。
    • ?:匹配一个任意字符。
    • [...]:匹配方括号内的任意一个字符。
    • [!...]:匹配除了方括号内的字符之外的任意一个字符。
  • 模式规则:用于定义一种模式,告诉 Make 工具如何将一类文件转换成另一类文件。例如:

    • %.o: %.c:表示所有以 .c 结尾的源文件都可以生成对应的 .o 目标文件。
  • 自动化变量:在规则的命令中使用,代表了与规则相关联的文件名。常用的自动化变量包括:

    • $@:表示规则中的目标文件名。
    • $<:表示规则中的第一个依赖文件名。
    • $^:表示规则中的所有依赖文件名,以空格分隔。

Makefile 的实际应用

  • 简单示例
    • 假设有一个简单的 C 项目,包含 main.cutils.c 两个源文件 ,目标是生成可执行文件 my_program。Makefile 内容如下:
      • CC = gcc
        CFLAGS = -Wall -g
        TARGET = my_program
        OBJECTS = main.o utils.o
        all: $(TARGET)
        $(TARGET): $(OBJECTS)
            $(CC) $(CFLAGS) -o $(TARGET) $(OBJECTS)
        main.o: main.c
            $(CC) $(CFLAGS) -c main.c
        utils.o: utils.c
            $(CC) $(CFLAGS) -c utils.c
        .PHONY: clean
        clean:
            rm -f $(TARGET) $(OBJECTS)
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
            - 在项目目录中执行 `make` 命令即可编译项目,执行 `make clean` 命令可以清理编译生成的文件。
        - **复杂项目**:
        - 对于复杂的嵌入式 Linux 项目,可能包含多个源文件、头文件、汇编文件等,需要更复杂的 Makefile 来管理。例如:
        - ```makefile
        CROSS_COMPILE ?= arm-linux-gnueabihf-
        TARGET ?= bsp
        CC := $(CROSS_COMPILE)gcc
        LD := $(CROSS_COMPILE)ld
        OBJCOPY := $(CROSS_COMPILE)objcopy
        OBJDUMP := $(CROSS_COMPILE)objdump
        INCDIRS := imx6ul \
        bsp/clk \
        bsp/led \
        bsp/delay
        SRCDIRS := project \
        bsp/clk \
        bsp/led \
        bsp/delay
        INCLUDE := $(patsubst %, -I %, $(INCDIRS))
        SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
        CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
        SFILENDIR := $(notdir $(SFILES))
        CFILENDIR := $(notdir $(CFILES))
        SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
        COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
        OBJS := $(SOBJS) $(COBJS)
        VPATH := $(SRCDIRS)
        .PHONY: clean
        $(TARGET).bin : $(OBJS)
        $(LD) -Timx6ul.lds -o $(TARGET).elf $^
        $(OBJCOPY) -O binary -S $(TARGET).elf $@
        $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
        $(SOBJS) : obj/%.o : %.S
        $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
        $(COBJS) : obj/%.o : %.c
        $(CC) -Wall -nostdlib -c -O2 $(INCLUDE) -o $@ $<
        clean:
        rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
    • 该 Makefile 使用了交叉编译工具链,支持多个源文件和汇编文件的编译,生成二进制可执行文件、ELF 格式文件和反汇编文件。

Makefile 的调试

  • 查看 make 过程:使用 make -n 命令可以显示将会执行的命令,但不会实际执行。
  • 增加详细输出:使用 make --debug 命令可以提供详细的调试信息,帮助排查问题。

Makefile 的高级功能

  • 条件语句:可以根据不同的条件执行不同的规则或设置变量。常见的条件语句有 ifeqifneqifdefifndef

    • 示例:根据操作系统设置不同的编译选项。
      • CC = gcc
        CFLAGS = -Wall -g
        ifeq ($(OS),Windows_NT)
            CFLAGS += -DWIN32
        else
            CFLAGS += -DUNIX
        endif
        TARGET = my_program
        SRCS = main.c utils.c
        all: $(TARGET)
        $(TARGET): $(SRCS)
            $(CC) $(CFLAGS) -o $(TARGET) $(SRCS)
        clean:
            rm -f $(TARGET)
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
            
        - **使用函数**:Makefile 提供了一些内建函数,帮助处理字符串和文件操作等任务。常用函数包括:
        - `$(wildcard pattern)`:返回匹配模式的所有文件。
        - `$(patsubst pattern,replacement,text)`:用指定的替换文本替换模式。
        - `$(basename names)`:去除文件名的扩展名部分。
        - `$(dir names)`:返回文件的目录路径。
        - 示例:使用 `wildcard` 和 `patsubst`。

        ---



        ### **Makefile基础笔记**

        #### **1. Makefile概述**
        - **Makefile的作用**:
        - 在Linux环境下,`make`命令会查找当前目录下的`Makefile`文件。
        - 根据Makefile中定义的规则,`make`可以自动化地执行命令,例如编译源代码、生成目标文件等。
        - Makefile的核心思想是通过定义规则来描述如何生成目标文件。

        - **基本逻辑示例**:
        - 假设有文件`a.txt`、`b.txt`和`c.txt`,需要先合并`a.txt`和`b.txt`生成中间文件`m.txt`,再将`m.txt`与`c.txt`合并生成最终文件`x.txt`。
        - Makefile通过规则定义这种依赖关系和生成过程。

        #### **2. Makefile规则的基本格式**
        - **规则格式**:

    目标文件: 依赖文件1 依赖文件2 …
    命令1
    命令2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
      - **目标文件**:需要生成的文件。
    - **依赖文件**:生成目标文件所需的文件。
    - **命令**:用于生成目标文件的具体命令,必须以Tab键开头。

    - **示例**:

    ```makefile
    m.txt: a.txt b.txt
    cat a.txt b.txt > m.txt

    x.txt: m.txt c.txt
    cat m.txt c.txt > x.txt
    • m.txt依赖于a.txtb.txt,通过cat命令合并生成。
    • x.txt依赖于m.txtc.txt,同样通过cat命令生成。

3. Makefile的执行逻辑

  • 增量编译

    • make会检查目标文件和依赖文件的修改时间。
    • 如果目标文件的修改时间晚于所有依赖文件,则认为目标文件是最新的,不会重新生成。
    • 如果依赖文件中的任何一个被修改,则会重新执行规则生成目标文件。
  • 默认规则

    • make默认执行Makefile中的第一条规则。
    • 例如,如果要生成x.txtmake会先检查m.txt是否存在,如果不存在则先生成m.txt,然后再生成x.txt

4. 伪目标(Phony Target)

  • 定义

    • 伪目标不是实际的文件名,而是用于执行特定任务的规则。
    • 例如,clean规则通常用于删除生成的文件。
  • 示例

    1
    2
    clean:
    rm -f m.txt x.txt
    • 执行make clean会删除m.txtx.txt
  • 避免冲突

    • 如果存在名为clean的文件,make clean可能不会执行。
    • 可以使用.PHONY声明伪目标:
      1
      2
      3
      .PHONY: clean
      clean:
      rm -f m.txt x.txt

5. 执行多条命令

  • 独立命令

    • 每条命令默认在独立的Shell环境中执行。
    • 例如,cd命令不会影响后续命令的执行环境。
  • 多条命令的写法

    • 使用;分隔命令:
      1
      2
      cd_ok:
      pwd; cd ..; pwd
    • 使用\换行:
      1
      2
      3
      4
      cd_ok:
      pwd; \
      cd ..; \
      pwd
    • 使用&&确保命令顺序执行:
      1
      2
      cd_ok:
      pwd && cd .. && pwd

6. 控制命令输出

  • 隐藏命令输出
    • 在命令前加@可以隐藏命令的打印输出,但命令仍然会执行。
    • 示例:
      1
      2
      3
      no_output:
      @echo 'not display'
      echo 'will display'

7. 错误处理

  • 默认行为

    • 如果命令返回非0值,make会中断执行并报错。
  • 忽略错误

    • 在命令前加-可以忽略错误,继续执行后续命令。
    • 示例:
      1
      2
      3
      ignore_error:
      -rm zzz.txt
      echo 'ok'

8. 总结

Makefile通过定义规则来自动化生成目标文件,核心在于描述目标文件与依赖文件之间的关系以及生成目标文件的具体命令。掌握以下要点:

  • 规则的基本格式:目标文件、依赖文件和命令。
  • 增量编译的逻辑:基于文件的修改时间。
  • 伪目标的使用:如clean
  • 多条命令的写法:使用;\&&
  • 控制命令输出:使用@
  • 错误处理:使用-忽略错误。


编译C程序 - Makefile教程笔记

1. C程序编译的基本步骤

编译C程序通常分为两步:

  1. 编译阶段:将每个.c文件编译为.o文件(目标文件)。
  2. 链接阶段:将所有.o文件链接为最终的可执行文件。

2. 示例项目结构

假设一个简单的C项目,包含以下文件:

  • hello.c:定义了一个hello函数。
  • hello.h:声明了hello函数。
  • main.c:主程序,调用了hello函数。

文件内容

  • **hello.c**:
    1
    2
    3
    4
    5
    6
    #include <stdio.h>

    int hello() {
    printf("hello, world!\n");
    return 0;
    }
  • **hello.h**:
    1
    int hello();
  • **main.c**:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include <stdio.h>
    #include "hello.h"

    int main() {
    printf("start...\n");
    hello();
    printf("exit.\n");
    return 0;
    }

3. Makefile编写

根据上述项目结构,Makefile可以定义如下规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 生成可执行文件
world.out: hello.o main.o
cc -o world.out hello.o main.o

# 编译 hello.c
hello.o: hello.c
cc -c hello.c

# 编译 main.c
main.o: main.c hello.h
cc -c main.c

# 清理生成的文件
clean:
rm -f *.o world.out

4. Makefile执行逻辑

  • 增量编译

    • make会根据文件的修改时间来判断是否需要重新编译。
    • 如果hello.c被修改,make会重新编译hello.c生成hello.o,并重新链接生成world.out
    • 如果hello.h被修改,make会重新编译所有依赖hello.h的文件(如main.c),并重新链接生成world.out
  • 执行过程

    • 初始运行make时,会依次执行以下步骤:
      1. 编译hello.c生成hello.o
      2. 编译main.c生成main.o
      3. 链接hello.omain.o生成world.out
    • 如果修改了hello.c,再次运行make时,只会重新编译hello.c并重新链接world.out
    • 如果修改了hello.hmake会重新编译main.c并重新链接world.out

5. 清理规则

  • clean规则
    • 用于删除所有生成的文件,包括.o文件和可执行文件。
    • 执行命令:make clean
    • 示例:
      1
      2
      clean:
      rm -f *.o world.out

6. 规则优化

随着项目规模的扩大,手动维护Makefile中的规则会变得繁琐。后续可以学习如何使用变量、模式规则等高级特性来简化Makefile的编写。

7. 小结

  • Makefile的作用:通过定义规则,make可以自动化编译C程序。
  • 规则的基本格式
    1
    2
    目标文件: 依赖文件
    命令
  • 增量编译make根据文件的修改时间来决定是否重新编译。
  • 清理规则:使用clean规则删除生成的文件,方便重新编译。

以下是根据廖雪峰的《使用隐式规则》教程整理的笔记,主要介绍了Makefile中隐式规则的概念、使用方法以及其潜在问题。


使用隐式规则 - Makefile教程笔记

1. 隐式规则的概念

  • 隐式规则(Implicit Rule)

    • Makefile中的一种特殊规则,用于自动推导目标文件的生成规则。
    • 当Makefile中没有明确定义某个目标文件的规则时,make会尝试使用内置的隐式规则来生成该目标文件。
  • 内置规则的作用

    • 为了简化Makefile的编写,make为常见的编译任务(如C、C++、ASM等)提供了默认的隐式规则。
    • 例如,对于C程序,make会自动应用以下隐式规则:
      1
      2
      xyz.o: xyz.c
      cc -c -o xyz.o xyz.c

2. 示例:隐式规则的应用

假设有一个C项目,包含hello.cmain.chello.h,目标是生成可执行文件world.out

项目结构

  • hello.c:定义了一个hello函数。
  • hello.h:声明了hello函数。
  • main.c:主程序,调用了hello函数。

Makefile

1
2
3
4
5
6
# 只保留生成 world.out 的规则
world.out: hello.o main.o
cc -o world.out hello.o main.o

clean:
rm -f *.o world.out

执行过程

  • 执行make命令时,make会自动推导出hello.omain.o的生成规则:
    • hello.o依赖于hello.c,使用cc -c -o hello.o hello.c生成。
    • main.o依赖于main.c,使用cc -c -o main.o main.c生成。
  • 最后,make会链接hello.omain.o生成world.out

输出

1
2
3
4
$ make
cc -c -o hello.o hello.c
cc -c -o main.o main.c
cc -o world.out hello.o main.o

3. 隐式规则的优势

  • 减少重复规则
    • 隐式规则可以减少Makefile中大量重复的编译规则。
    • 例如,对于多个.c文件,无需为每个文件单独编写.o文件的生成规则。

4. 隐式规则的潜在问题

  • 无法跟踪头文件的修改
    • 隐式规则的一个主要问题是无法自动跟踪头文件(如.h文件)的修改。
    • 例如,如果修改了hello.h,隐式规则main.o: main.c不会自动检测到hello.h的修改,导致main.c不会被重新编译。
    • 这可能导致生成的可执行文件中包含过时的代码。

5. 解决隐式规则的潜在问题

  • 手动添加依赖

    • 为了确保头文件的修改能够触发重新编译,需要手动在Makefile中添加头文件的依赖关系。
    • 例如:
      1
      2
      main.o: main.c hello.h
      cc -c main.c
  • 自动生成依赖文件

    • 在实际项目中,可以通过工具(如gcc -M)自动生成依赖文件,并将其包含在Makefile中。
    • 例如,使用gcc -M生成依赖文件:
      1
      gcc -M main.c > main.d
    • 然后在Makefile中包含这些依赖文件:
      1
      -include main.d

6. 小结

  • 隐式规则的作用:减少重复的编译规则,简化Makefile的编写。
  • 隐式规则的潜在问题:无法自动跟踪头文件的修改,可能导致生成的可执行文件包含过时代码。
  • 解决方法:手动添加头文件依赖或使用工具自动生成依赖文件。

以下是根据廖雪峰的《使用变量》教程整理的笔记,主要介绍了Makefile中变量的使用方法、内置变量和自动变量的概念。


使用变量 - Makefile教程笔记

1. 变量的作用

  • 解决重复问题
    • 在Makefile中,文件名或命令可能会重复出现多次,手动修改容易出错。
    • 使用变量可以简化Makefile的编写,提高可维护性。

2. 定义和使用变量

  • 定义变量

    • 使用变量名 = 值变量名 := 值定义变量。
    • 通常变量名使用全大写,例如TARGETOBJS等。
    • 示例:
      1
      2
      TARGET = world.out
      OBJS = hello.o main.o
  • 引用变量

    • 使用$(变量名)引用变量。
    • 示例:
      1
      2
      $(TARGET): $(OBJS)
      cc -o $(TARGET) $(OBJS)

3. 动态生成变量

  • 使用wildcard函数

    • wildcard函数可以匹配当前目录下的文件模式。
    • 示例:$(wildcard *.c)会列出当前目录下所有.c文件。
    • 示例:
      1
      OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
  • 使用patsubst函数

    • patsubst函数用于模式替换。
    • 示例:$(patsubst %.c,%.o,$(wildcard *.c))会将所有.c文件名替换为.o文件名。
    • 这样,每当添加新的.c文件时,OBJS变量会自动更新,无需手动修改Makefile。

4. 内置变量

  • 内置变量

    • make提供了一些内置变量,例如$(CC)表示C编译器,默认值是cc
    • 示例:
      1
      2
      $(TARGET): $(OBJS)
      $(CC) -o $(TARGET) $(OBJS)
  • 修改内置变量

    • 可以重新定义内置变量的值。
    • 示例:使用交叉编译器时,可以设置CC = riscv64-linux-gnu-gcc

5. 自动变量

  • 自动变量

    • 在规则中自动指向特定值的变量。
    • 常用的自动变量:
      • $@:目标文件名。
      • $<:依赖列表中的第一个文件。
      • $^:所有依赖文件。
      • 示例:
        1
        2
        3
        4
        5
        world.out: hello.o main.o
        @echo '$$@ = $@' # 目标文件名
        @echo '$$< = $<' # 第一个依赖文件
        @echo '$$^ = $^' # 所有依赖文件
        $(CC) -o $@ $^
  • 输出示例

    1
    2
    3
    4
    $@ = world.out
    $< = hello.o
    $^ = hello.o main.o
    $(CC) -o world.out hello.o main.o

6. 变量的调试

  • 打印变量
    • 使用@echo打印变量的值,便于调试。
    • 示例:
      1
      2
      3
      4
      5
      world.out: hello.o main.o
      @echo '$$@ = $@'
      @echo '$$< = $<'
      @echo '$$^ = $^'
      $(CC) -o $@ $^

7. 小结

  • 变量的作用:简化Makefile的编写,减少重复,提高可维护性。
  • 变量的定义和引用:使用变量名 = 值定义,使用$(变量名)引用。
  • 动态生成变量:使用wildcardpatsubst函数自动生成文件列表。
  • 内置变量:使用内置变量(如$(CC))简化命令。
  • 自动变量:使用自动变量(如$@$<$^)简化规则。