2. Makefile.md 25 KB


title: Makefile created: 2026-08-31 type: source tags:

  • makefile
  • build
  • linux
  • embedded ---

Makefile 详解

一、为什么需要 Makefile

1.1 手动编译的痛点

# 小项目手动编译
gcc main.c utils.c -o app

# 大项目手动编译(噩梦)
gcc src/main.c src/utils.c src/network.c src/crypto.c \
    -I./include -L./lib -lssl -lcrypto -lpthread -o app

# 修改一个文件后,全部重新编译 → 浪费时间

1.2 Makefile 解决的问题

问题 Makefile 的解决方案
编译命令繁琐 一条 make 搞定
全量编译浪费 增量编译:只重建修改过的文件及其依赖
依赖关系混乱 显式声明文件间依赖关系
多目标管理 make cleanmake install 等伪目标
跨平台/跨工具链 变量机制,换工具链只需改一个变量

二、Makefile 基本规则

2.1 规则语法

target: prereq1 prereq2
	command1
	command2
  • target:目标文件(通常是 .o 或可执行文件)
  • prereq(prerequisites):依赖文件
  • command:构建命令(必须以 Tab 开头,不能用空格

2.2 make 的执行逻辑

1. 读取 Makefile
2. 找到第一个目标(默认目标)
3. 检查目标是否存在、依赖是否比目标新
4. 如果依赖更新或目标不存在 → 执行命令
5. 递归处理依赖(如果依赖也是目标)

核心机制:时间戳比较

如果 依赖文件的修改时间 > 目标文件的修改时间 → 重建目标
否则 → 跳过(认为目标已是最新)

2.3 最小 Makefile 示例

# 一个完整的、可编译的最小示例
app: main.o utils.o
	gcc main.o utils.o -o app

main.o: main.c utils.h
	gcc -c main.c -o main.o

utils.o: utils.c utils.h
	gcc -c utils.c -o utils.o

clean:
	rm -f *.o app
# 使用
make          # 编译
make clean    # 清理

三、变量

3.1 四种赋值方式

运算符 名称 行为 示例
= 延迟展开 使用时才求值 OBJS = $(SRC:.c=.o)
:= 立即展开 赋值时立刻求值 OBJS := main.o utils.o
?= 条件赋值 仅当变量未定义时赋值 CC ?= gcc
+= 追加 在现有值后追加内容 CFLAGS += -Wall
# 延迟展开 vs 立即展开
CC = gcc                    # 延迟展开(用时求值)
OBJS := main.o utils.o      # 立即展开(定义时求值)

# 条件赋值:允许外部覆盖
CC ?= gcc                   # 默认 gcc,但 make CC=clang 可覆盖

# 追加
CFLAGS = -Wall
CFLAGS += -O2               # 现在 CFLAGS = "-Wall -O2"

3.2 变量引用

CC = gcc
CFLAGS = -Wall -O2

# 引用变量
$(CC) $(CFLAGS) main.c -o main
# 等价于
gcc -Wall -O2 main.c -o main

四、自动变量

变量 含义 示例
$@ 当前目标 app: main.ogcc main.o -o $@
$< 第一个依赖 main.o: main.cgcc -c $< -o $@
$^ 所有依赖(去重) app: a.o b.ogcc $^ -o $@
$? 比目标新的依赖 只传递修改过的文件
$* 模式匹配的词干 %.o: %.c 中,$* 是不带后缀的文件名
# 使用自动变量改写
app: main.o utils.o
	gcc $^ -o $@           # gcc main.o utils.o -o app

main.o: main.c utils.h
	gcc -c $< -o $@        # gcc -c main.c -o main.o

五、通配符与模式规则

5.1 通配符 %*

符号 使用场景 含义
% 规则中 匹配任意长度的文件名前缀(词干)
* 文件名中 Shell 风格通配符,匹配任意字符
# * 通配符 — 在变量或依赖中匹配文件
SRCS = $(wildcard src/*.c)          # 获取 src/ 下所有 .c 文件
OBJS = $(SRCS:.c=.o)                # 将 .c 替换为 .o

# % 通配符 — 在规则中匹配
%.o: %.c
	gcc -c $< -o $@

5.2 模式规则

# 所有 .c → .o 的通用规则
%.o: %.c
	gcc -c $< -o $@

# 指定依赖
%.o: %.c include/*.h
	gcc -Wall -O2 -c $< -o $@

5.3 自动推导规则

Make 内置了隐含规则:

# 以下规则可以省略(make 自动推导)
main.o: main.c
	# make 自动执行 gcc -c main.c -o main.o

建议: 始终显式写出规则,避免依赖隐含行为。

六、多目录编译

6.1 常用函数

函数 作用 示例
$(wildcard pat) 匹配文件 $(wildcard src/*.c)
$(patsubst p,r,t) 模式替换 $(patsubst %.c,%.o,src/*.c)
$(notdir names) 取文件名(去掉路径) $(notdir src/main.c)main.c
$(dir names) 取目录 $(dir src/main.c)src/
$(foreach v,l,t) 循环 $(foreach d,$(DIRS),$(wildcard $(d)/*.c))

6.2 多目录项目完整示例

# 项目结构:
# project/
# ├── include/          # 头文件
# │   ├── utils.h
# │   └── network.h
# ├── src/              # 源文件
# │   ├── main.c
# │   ├── utils.c
# │   └── network/
# │       ├── tcp.c
# │       └── udp.c
# ├── lib/              # 外部库
# └── Makefile

CC = gcc
CFLAGS = -Wall -O2 -I./include

# 递归查找所有 .c 文件
SRCS = $(shell find src -name '*.c')
OBJS = $(SRCS:.c=.o)

# 自动创建输出目录
OBJDIR = build
$(shell mkdir -p $(OBJDIR))

TARGET = app

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $^ -o $@

# 模式规则:所有 .c 编译为 .o,放入 build 目录
$(OBJDIR)/%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

clean:
	rm -rf $(OBJDIR) $(TARGET)

.PHONY: all clean

七、嵌入式交叉编译 Makefile 模板

# ============================================
# 嵌入式交叉编译 Makefile 模板
# ============================================

# 交叉编译工具链
CROSS_COMPILE ?= arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
AR = $(CROSS_COMPILE)ar
STRIP = $(CROSS_COMPILE)strip

# 编译选项
CFLAGS = -Wall -O2 -g
CFLAGS += -I./include
CFLAGS += -DDEBUG -DVERSION=\"1.0\"
CFLAGS += -std=c11

# 链接选项
LDFLAGS = -static
LIBS = -lpthread

# 目标
TARGET = app

# 源文件与目标文件
SRCS = $(wildcard src/*.c)
OBJS = $(SRCS:.c=.o)

# 默认目标
all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
	$(STRIP) $@              # 去掉调试符号,减小体积
	@echo "Build complete: $(TARGET)"

# 通用编译规则
%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

# 头文件依赖自动生成(-MMD -MP)
-include $(OBJS:.o=.d)

%.o: %.c
	$(CC) $(CFLAGS) -MMD -MP -c -o $@ $<

# 清理
clean:
	rm -f $(OBJS) $(OBJS:.o=.d) $(TARGET)

# 安装(通常用于根文件系统部署)
DESTDIR ?= /usr/local
install: $(TARGET)
	install -d $(DESTDIR)/bin
	install -m 755 $(TARGET) $(DESTDIR)/bin/

.PHONY: all clean install

头文件依赖自动生成原理

# -MMD 生成 .d 文件(依赖关系),如 main.d:
# main.o: main.c utils.h config.h
# -MP 生成空规则防止头文件删除时报错
-include $(OBJS:.o=.d)

# 每次编译时自动生成依赖,下次 make 自动检查头文件变化

八、常用 Makefile 技巧

8.1 伪目标

.PHONY: all clean install test

all: $(TARGET)
clean:
	rm -f $(OBJS) $(TARGET)
install:
	cp $(TARGET) /usr/local/bin/
test:
	./run_tests.sh

为什么要声明 .PHONY 如果当前目录下有名为 clean 的文件,make clean 会因文件已存在而跳过。声明伪目标可避免此问题。

8.2 条件判断

# 根据平台选择不同选项
ifeq ($(OS),Windows_NT)
    TARGET = app.exe
    RM = del /Q
else
    TARGET = app
    RM = rm -f
endif

# 根据编译类型选择
ifdef DEBUG
    CFLAGS += -g -O0
else
    CFLAGS += -O2
endif
# 使用
make DEBUG=1    # 调试版本
make            # 发布版本

8.3 函数与字符串处理

SRCS = src/main.c src/utils.c src/net/tcp.c

# 取所有文件名(去掉路径)
$(notdir $(SRCS))           # → main.c utils.c tcp.c

# 模式替换
$(patsubst %.c,%.o,$(SRCS)) # → src/main.o src/utils.o src/net/tcp.o

# 简写
$(SRCS:.c=.o)               # → 同上

# 循环
DIRS = src include lib
$(foreach d,$(DIRS),$(wildcard $(d)/*.c))

8.4 静态模式规则

OBJS = main.o utils.o network.o

# 只对这些特定文件应用规则
$(OBJS): %.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

8.5 多目标规则

# 同一规则生成多个目标
all: libfoo.so libfoo.a

libfoo.so: foo.o
	$(CC) -shared -o $@ $^

libfoo.a: foo.o
	$(AR) rcs $@ $^

8.6 自动创建目录

# 在编译前确保目录存在
OBJDIR = build
$(shell mkdir -p $(OBJDIR))

$(OBJDIR)/%.o: %.c
	$(CC) -c $< -o $@

8.7 打印调试信息

# 打印变量值,排查 Makefile 问题
all:
	@echo "SRCS = $(SRCS)"
	@echo "OBJS = $(OBJS)"
	@echo "CC = $(CC)"

九、深度实例:i.MX6ULL ARM 裸机 Makefile 逐行解析

以下是一个真实的嵌入式裸机项目 Makefile,涵盖了多目录编译、交叉编译、链接脚本、反汇编等生产级特性。

9.1 项目目录结构

project/
├── imx6ul/                  # 芯片级头文件(寄存器定义等)
│   ├── imx6ul.h
│   └── fsl_common.h
├── bsp/                     # 板级支持包
│   ├── clk/                 # 时钟驱动
│   │   ├── bsp_clk.h
│   │   └── bsp_clk.c
│   ├── led/                 # LED 驱动
│   │   ├── bsp_led.h
│   │   └── bsp_led.c
│   └── delay/               # 延时驱动
│       ├── bsp_delay.h
│       └── bsp_delay.c
├── project/                 # 应用入口
│   └── main.c
├── imx6ul.lds               # 链接脚本(定义内存布局)
├── obj/                     # 编译输出目录(.o 文件)
└── Makefile

9.2 完整 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)

9.3 逐段深度解析

第一段:交叉编译工具链定义

CROSS_COMPILE 	?= arm-linux-gnueabihf-
TARGET		  	?= bsp

CC 				:= $(CROSS_COMPILE)gcc
LD				:= $(CROSS_COMPILE)ld
OBJCOPY 		:= $(CROSS_COMPILE)objcopy
OBJDUMP 		:= $(CROSS_COMPILE)objdump
变量 赋值方式 作用
CROSS_COMPILE ?= 条件赋值,允许外部覆盖。make CROSS_COMPILE=aarch64-linux-gnu- 可切换工具链
TARGET ?= 同上,默认目标名为 bsp,可外部指定
CC := 立即展开,派生出 arm-linux-gnueabihf-gcc
LD := 链接器,用于将 .o 链接为 .elf
OBJCOPY := 格式转换工具,将 .elf 转为裸二进制 .bin
OBJDUMP := 反汇编工具,生成 .dis 文件供调试分析

为什么用 ?= 而不是 := ?= 允许用户在命令行覆盖,例如 make CROSS_COMPILE=arm-linux-gnueabihf-,方便在同一台机器上切换不同工具链。

第二段:多目录源文件与头文件收集

INCDIRS 		:= imx6ul \
				   bsp/clk \
				   bsp/led \
				   bsp/delay

SRCDIRS			:= project \
				   bsp/clk \
				   bsp/led \
				   bsp/delay

INCDIRSSRCDIRS 分别列出所有头文件目录和源文件目录。裸机项目通常按功能模块拆分目录,每个模块包含自己的 .c.h 文件。

第三段:生成 -I 编译选项

INCLUDE			:= $(patsubst %, -I %, $(INCDIRS))

patsubst 执行过程:

输入: imx6ul bsp/clk bsp/led bsp/delay
模式: % → -I %
输出: -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay

最终 INCLUDE 变量展开为编译器的头文件搜索路径参数。

第四段:递归收集所有源文件

SFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))

展开过程(以 CFILES 为例):

SRCDIRS = project bsp/clk bsp/led bsp/delay

$(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))

→ $(wildcard project/*.c)      → project/main.c
  $(wildcard bsp/clk/*.c)      → bsp/clk/bsp_clk.c
  $(wildcard bsp/led/*.c)      → bsp/led/bsp_led.c
  $(wildcard bsp/delay/*.c)    → bsp/delay/bsp_delay.c

最终: CFILES = project/main.c bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c

foreach + wildcard 组合是多目录项目自动收集源文件的标准模式。

第五段:剥离路径、生成目标文件列表

SFILENDIR		:= $(notdir  $(SFILES))
CFILENDIR		:= $(notdir  $(CFILES))

SOBJS			:= $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS			:= $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS			:= $(SOBJS) $(COBJS)

逐步展开:

CFILES = project/main.c bsp/clk/bsp_clk.c bsp/led/bsp_led.c bsp/delay/bsp_delay.c

① notdir: 去掉路径前缀
CFILENDIR = main.c bsp_clk.c bsp_led.c bsp_delay.c

② 后缀替换: .c → .o
CFILENDIR:.c=.o → main.o bsp_clk.o bsp_led.o bsp_delay.o

③ patsubst: 加上 obj/ 前缀
COBJS = obj/main.o obj/bsp_clk.o obj/bsp_led.o obj/bsp_delay.o

所有 .o 文件统一输出到 obj/ 目录,保持源码目录干净。

第六段:VPATH — 源文件搜索路径

VPATH			:= $(SRCDIRS)

这是本 Makefile 最关键的设计之一。

VPATH 告诉 make:当在当前目录找不到某个依赖文件时,依次去 projectbsp/clkbsp/ledbsp/delay 目录下搜索。

为什么需要 VPATH?

目标文件统一放在 obj/ 目录(如 obj/main.o),但源文件分散在多个子目录。模式规则写的是:

$(COBJS) : obj/%.o : %.c

make 看到 obj/main.o 需要 main.c,但 main.c 不在当前目录。VPATH 让 make 去 project/ 目录下找到它,然后执行:

arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -I imx6ul -I bsp/clk ... -o obj/main.o project/main.c

没有 VPATH 会怎样? make 会报错 No rule to make target 'main.c', needed by 'obj/main.o'

第七段:静态模式规则 — 分离 .S 和 .c 编译

$(SOBJS) : obj/%.o : %.S
	$(CC) -Wall -nostdlib -c -O2  $(INCLUDE) -o $@ $<

$(COBJS) : obj/%.o : %.c
	$(CC) -Wall -nostdlib -c -O2  $(INCLUDE) -o $@ $<

静态模式规则语法:

$(targets): pattern: prereqs
	command
  • $(SOBJS) 限定此规则只对 SOBJS 列表中的目标生效
  • obj/%.o : %.S 是模式部分,% 匹配文件名词干(如 start
  • $@ = obj/start.o(当前目标)
  • $< = start.S(第一个依赖,由 VPATH 解析)

-nostdlib 的含义: 不链接标准库(libc、libgcc 等),因为裸机环境没有操作系统提供的 C 运行时库。

第八段:链接与输出 — 三步生成最终产物

$(TARGET).bin : $(OBJS)
	$(LD) -Timx6ul.lds -o $(TARGET).elf $^
	$(OBJCOPY) -O binary -S $(TARGET).elf $@
	$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
步骤 命令 作用
1 ld -Timx6ul.lds -o bsp.elf $^ 用链接脚本将所有 .o 链接为 ELF 可执行文件
2 objcopy -O binary -S bsp.elf bsp.bin 从 ELF 提取裸二进制,用于烧录到 Flash
3 objdump -D -m arm bsp.elf > bsp.dis 反汇编全部内容,生成 .dis 文件供调试分析

链接脚本 imx6ul.lds 的作用: 定义内存布局——代码段放哪里、数据段放哪里、栈的起始地址等。裸机项目没有操作系统的内存管理,必须手动指定。

为什么需要 .bin ELF 是 Linux 可执行文件格式,包含元数据。烧录到裸机 Flash 时需要纯二进制 .bin(不含 ELF 头、段表等)。

第九段:清理目标

clean:
	rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)

删除所有生成物,恢复到干净状态。

9.4 构建流程图

                    ┌─────────────────────────────────┐
                    │          make 执行流程            │
                    └─────────────────────────────────┘
                                    │
                    ┌───────────────▼───────────────┐
                    │  1. 默认目标: bsp.bin          │
                    │     依赖: $(OBJS)              │
                    └───────────────┬───────────────┘
                                    │
                    ┌───────────────▼───────────────┐
                    │  2. 检查 OBJS 中每个 .o 文件   │
                    │     obj/main.o → main.c (VPATH)│
                    │     obj/bsp_clk.o → bsp/clk/  │
                    │     obj/bsp_led.o → bsp/led/  │
                    │     ...                        │
                    └───────────────┬───────────────┘
                                    │
                    ┌───────────────▼───────────────┐
                    │  3. 模式规则编译               │
                    │     .c → obj/*.o (gcc -c)     │
                    │     .S → obj/*.o (gcc -c)     │
                    └───────────────┬───────────────┘
                                    │
                    ┌───────────────▼───────────────┐
                    │  4. 链接                       │
                    │     ld → bsp.elf              │
                    └───────────────┬───────────────┘
                                    │
                    ┌───────────────▼───────────────┐
                    │  5. 格式转换                   │
                    │     objcopy → bsp.bin (烧录用) │
                    │     objdump → bsp.dis (调试用) │
                    └───────────────────────────────┘

9.5 关键技巧总结

技巧 本例用法 解决的问题
?= 条件赋值 CROSS_COMPILE ?= 允许命令行覆盖工具链
foreach + wildcard 自动收集多目录源文件 无需手动列出每个文件
notdir + patsubst 生成 obj/ 前缀的目标列表 统一输出目录,保持源码整洁
VPATH 指定源文件搜索路径 解决目标与源文件路径分离的问题
静态模式规则 分别处理 .S.c 汇编和 C 用不同编译选项
链接脚本 -T imx6ul.lds 裸机项目的内存布局定义
objcopy ELF → bin 生成可烧录的裸二进制
objdump -D 生成反汇编 调试时对照源码分析

十、完整项目示例

10.1 嵌入式 LED 控制项目

# ============================================
# Makefile — LED 控制应用(嵌入式交叉编译)
# ============================================

CROSS_COMPILE ?= arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
STRIP = $(CROSS_COMPILE)strip

CFLAGS = -Wall -O2 -g -I./include
CFLAGS += -DUSE_GPIO -DDEVICE=\"/dev/gpio0\"
LDFLAGS = -static

TARGET = led_app
SRCS = src/main.c src/led.c src/gpio.c
OBJS = $(SRCS:.c=.o)

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^
	$(STRIP) $@
	@echo "=== Build complete: $(TARGET) ==="

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

clean:
	rm -f $(OBJS) $(TARGET)

# 烧录到设备(示例)
deploy: $(TARGET)
	scp $(TARGET) root@192.168.1.100:/usr/bin/
	ssh root@192.168.1.100 "chmod +x /usr/bin/$(TARGET)"

.PHONY: all clean deploy

十一、注意事项

  1. Tab 缩进是强制的:命令行前必须用 Tab,不能用空格(最常见的错误)
  2. @ 前缀@echo 不打印命令本身,只打印输出
  3. - 前缀-rm 表示忽略错误继续执行
  4. $ 要转义:在 Shell 命令中用 $$ 表示 Shell 变量,如 $$HOME
  5. 并行编译make -j4 开启 4 个并行任务,大幅加速编译
  6. make 默认执行第一个目标:将 all 放在最前面
  7. 依赖自动生成:用 -MMD -MP 避免手动维护头文件依赖

十二、面试要点

Q1: Make 的核心机制是什么?

A: 基于时间戳的增量编译。make 比较目标文件和依赖文件的修改时间,如果依赖更新则重建目标。只重建需要更新的部分,避免全量编译。

Q2: =:= 的区别?

A: = 延迟展开(使用时求值),:= 立即展开(定义时求值)。OBJS = $(SRC:.c=.o)= 时,SRC 后续修改会影响 OBJS;用 := 则不会。

Q3: 什么是自动变量?$@$<$^ 分别是什么?

A: 自动变量在规则执行时自动赋值:$@ 是当前目标,$< 是第一个依赖,$^ 是所有依赖(去重)。用自动变量可以写出通用的模式规则。

Q4: 如何实现多目录编译?

A:$(wildcard src/**/*.c)$(shell find src -name '*.c') 递归查找源文件,用模式规则 %.o: %.c 配合 -o $@ 输出到对应目录,用 -MMD -MP 自动生成头文件依赖。

Q5: -MMD -MP 的作用?

A: -MMD 在编译时自动生成 .d 依赖文件(记录目标依赖哪些头文件),-MP 生成空规则防止头文件被删除后 make 报错。通过 -include $(OBJS:.o=.d) 引入,实现头文件变更时自动重新编译。

Q6: 为什么命令前必须用 Tab?

A: 这是 make 的语法约定。make 用 Tab 来区分规则定义和命令。如果用空格缩进,make 会将其误认为规则的一部分而报语法错误。

Q7: 交叉编译 Makefile 需要注意什么?

A: 1)设置正确的 CROSS_COMPILE 前缀;2)使用 -static 避免目标设备缺动态库;3)交叉编译的工具链路径需正确;4)可能需要指定 --sysroot 指向目标系统的根文件系统。

Q8: 如何调试 Makefile?

A: 1)make -n(dry-run)打印要执行的命令但不执行;2)make -p 打印完整规则数据库;3)在规则中加 @echo 打印变量值;4)make --debug=b 显示决策原因。