makefile中使用:变量、%、$@、exprot

1、Makefile文件

复制代码
# ===================================================================
# 文件名:Makefile.txt
# 作用:顶层构建调度器,负责遍历所有模块子目录并递归调用 make
# 一、规则不同
#  1.在 Testlib: 规则下:
#  这是"默认构建规则"。当你直接输入 make 或 make Testlib 时,Make 会执行这个规则。
#  它的目的是编译生成最终的库/可执行文件。
#  2.在 %: 通配规则下:
#  这是一个"通配规则",匹配所有未在 Makefile 中显式定义的目标名。当
#  你输入 make clean、make install、make distclean 或 make foo 时,Make 会触发这个规则。
#  它的目的是将你敲的命令(比如 clean)传递给所有子模块。

# 二、传递目标参数不同
#  1.在 Testlib 规则中:$@ 的值是固定的 Testlib。
#  所以实际执行的是:make -C SUBA Testlib,告诉 SUBA 子模块去构建 Testlib 这个目标。
#  2.在 % 规则中:$@ 的值是你输入的命令。
#  比如你敲 make clean,那么 % 匹配到 clean,$@ 就是 clean,
#  实际执行的是:make -C SUBA clean,告诉 SUBA 子模块去执行清理操作。

# 三、为什么写2遍类似文本?
#  如果只写 Testlib 规则,不写 % 规则:你执行 make clean 时,Make 找不到匹配 clean 的规则,就会报错
#  如果只写 % 规则,不写 Testlib 规则:你执行 make(默认目标)时,由于 all 依赖 Testlib,Make 会去查找 Testlib 目标。

# =》总结:这2行命令(文本相同,但使命不同):
#  Testlib 里的作用是:"构建"时,让 SUBA 模块编译主目标。
#  % 里的作用是:"维护"时(如清理、安装),让 SUBA 模块执行对应的辅助操作。
# ===================================================================
 
 # make 找到默认目标 all,它依赖 swslib,所以开始构建 swslib;
 # 在 swslib 规则中,按书写顺序依次调用子目录;
 # 即执行 make 时,默认构建 Testlib 目标。
all: Testlib
 # -------------------------------------------------------------------
 # 目标 Testlib:构建主库/主程序(遍历所有基础模块)
 # -------------------------------------------------------------------
Testlib:

	# 分号; 表示这是三条独立的 Shell 命令按顺序执行。
	# 设置环境变量 VAR=SUBA,导出,然后进入 SUBA 子目录执行 make,
	# 目标为当前目标名(即 Testlib)。子目录 Makefile 会根据 VAR 值编译对应模块。
	# export VAR作用:export VAR 之后,子进程确实能看到这个变量。
	#   在 Linux 中,每个进程(比如你当前的 Shell)都有一块专属的内存空间,里面存放着环境变量表 
    #   (就是一个 key=value 的字符串数组,比如 PATH=/usr/bin、HOME=/root。
	#	环境变量是父进程留给子进程的"遗产",只有被 export 标记过的遗产,法律(操作系统)才允许传 
    #给后代;没标记的,只能父进程自己用,子进程无权继承。这就是为什么在递归 Makefile 中,export 是必不可少的"传话筒"。
	@VAR=SUBA;export VAR;$(MAKE) -C SUBA $@
	# VAR=SUBB,进入 SUBB 目录编译
	@VAR=SUBB;export   VAR;$(MAKE) -C SUBB $@
	
	
# -------------------------------------------------------------------
# 通配目标 %:匹配任何未在 above 中显式指定的目标(如 make clean)
# -------------------------------------------------------------------	
%:
	@VAR=SUBA;export VAR;$(MAKE) -C SUBA $@
	@VAR=SUBB;export   VAR;$(MAKE) -C SUBB $@

2、注解

export VAR的核心作用是:将当前 Shell 中的变量 VAR提升为"环境变量",子进程就能看到吗?环境变量和子进程有什么关联?

你的理解完全正确!export VAR 之后,子进程确实能看到这个变量。

为了让你彻底搞懂环境变量和子进程之间的底层关联,我用"传纸条"和"复印机"来给你打个比方,再配合 Linux 内核的底层机制讲解:

1. 环境变量的本质("传纸条")

在 Linux 中,每个进程(比如你当前的 Shell)都有一块专属的内存空间,里面存放着环境变量表(就是一个 key=value 的字符串数组,比如 PATH=/usr/bin、HOME=/root)。

如果你没有 export(只写了 VAR=SUBA):这个变量只写在了 Shell 的"内部草稿纸"上,不属于"环境变量表"。

如果你执行了 export:Shell 会把这个 VAR=SUBA抄写到那张正式的"环境变量表(传纸条)"里。

2. 进程创建的底层机制("复印机与遗传")

当你在 Shell 里输入 make 并敲回车时,操作系统(Linux内核)会通过两个步骤创建子进程:

fork()(复印):内核会把当前 Shell 进程的所有资源(包括代码、数据、以及那张正式的环境变量表)完整地克隆一份,产生一个完全一样的新进程(子进程)。

execve()(替换):子进程发现自己是 make 程序,于是把自己的代码替换成 make 的代码,但保留了从父进程克隆过来的那张环境变量表。

关键点来了:execve 系统调用有一个参数就是envp\[\](环境变量指针)。只有被父进程 export 并挂在"环境变量表"上的变量,才会被 fork 复制,进而被 execve 保留下来。

如果你没 export,VAR=SUBA只存在于 Shell 的"内部草稿纸"上,fork 时根本没机会被复印到子进程中。子进程启动后,在自己的内存里完全找不到 VAR 这个变量,所以子 Makefile 里的 $(VAR) 就是空的。

3. 为什么 Makefile 里特别依赖这个机制?

Makefile 的执行逻辑并不是一次性读完的,它是一个递归解析的过程。

父 Make 在执行到 $(MAKE) -C SUBA时,会先启动一个 Shell 子进程来解析这行命令。

这个 Shell 子进程如果发现 VAR 是 export 的,就会把它复印给 make 子进程。

make 子进程拿到这个环境变量后,会把它自动转换成 Makefile 内部的变量 $(VAR)。

如果没有 export,父 Make 虽然知道 VAR=SUBA,但当它去执行 -C SUBA 的子 Make 时,子 Make 根本收不到这条信息,导致路径拼接失败(找不到 label.c)。

补充一个容易混淆的坑

你可能注意到你在 Makefile.txt 中写的是:

复制代码
makefile

@VAR=SUBA;export VAR;$(MAKE) -C SUBA $@

这里有一个细节:分号 ; 表示这是三条 独立的 Shell命令按顺序执行

如果不加 export,第二条命令 (MAKE) 启动时,VAR 仅仅是当前 Shell 进程的局部变量。加上 export 后,当前 Shell 会把它放进自己的环境表,这样当 (MAKE) 作为子进程被启动时,才能继承过去。

**总结一句话:**环境变量是父进程留给子进程的"遗产",只有被 export 标记过的遗产,法律(操作系统)才允许传给后代;没标记的,只能父进程自己用,子进程无权继承。这就是为什么在递归 Makefile 中,export 是必不可少的"传话筒"。