M4 宏处理器

一、m4 是什么? #

GUN m4 是一种通用的 宏处理器(macro processor),最初是 Unix 标准工具的一部分,后来 GNU 也实现了自己的版本(GNU m4)。 它的名字来自 “macro”,设计思想和 C 语言的预处理器(cpp)类似,但更通用、可编程、与语言无关。

🔧 简单理解: #

m4 是一个「文本宏展开器」: 它读入文本 → 按定义替换宏 → 输出展开后的文本。

但 m4 比 C 预处理器灵活得多——它本身是一种“小型解释语言”,带有:

  • 宏定义与展开机制;
  • 条件判断(ifdef, ifelse);
  • 循环、递归
  • 参数替换、字符串操作、文件包含
  • 可生成任意文本(代码、配置、脚本)

二、m4 解决了什么问题? #

在 1970–80 年代,Unix 世界里还没有复杂的模板引擎、元编程机制,很多项目要面对:

  • 各种 平台差异(操作系统、编译器特性不同);
  • 重复性代码生成
  • 配置文件或源代码模板 需要根据条件生成;
  • 轻量级宏系统 需要在编译前阶段运行。

m4 的出现正是为了在「正式编译前」进行灵活的文本层面元编程。

三、历史上最典型的用途 #

1️⃣ autoconf 系统的基础 #

这是 m4 最著名的应用场景。

  • GNU 的 autoconf(用于生成 configure 脚本)本质上就是一堆 .m4 宏定义。
  • configure.ac 文件会通过 autoconf → m4 → 生成最终的 configure。
  • 它负责检测系统特性、生成 Makefile、适配不同环境。

没有 m4,就没有 autoconf。

2️⃣ sendmail 的配置系统 #

  • sendmail.cf 语法非常复杂,人类几乎无法直接写。
  • 因此它使用 m4 模板(sendmail.mc)来生成最终配置。
  • 管理员通过修改 .mc 文件,然后运行 m4 展开生成 .cf。

3️⃣ 早期代码生成器 / DSL #

  • 有人用 m4 生成 C/C++/Fortran 代码;
  • 或者生成 shell 脚本、汇编代码;
  • 在没有模板引擎或 Jinja2 的时代,它是「文本元编程的瑞士军刀」。

四、语法示例 #

define(`HELLO', `Hello, world!')
HELLO

运行:

m4 hello.m4

输出:

Hello, world!

再看带参数的宏:

define(`greet', `Hello, $1!')
greet(`Alice')
greet(`Bob')

输出:

Hello, Alice!
Hello, Bob!

甚至可以写 if 逻辑:

define(`say', `ifelse($1, `yes', `You said yes', `You said no')')
say(`yes')
say(`no')

输出:

You said yes
You said no

五、现在还有实际用途吗? #

🧭 少了,但没完全消失。

✅ 仍有用的场景: #

  1. autoconf 系统仍在大量使用 所有基于 autotools 的开源项目(GNU coreutils、bash、curl 等)依然在用 m4 作为底层宏引擎。 👉 对这些项目贡献代码时,你会接触 .m4 文件。
  2. 极少数系统配置(如 sendmail)仍用 m4
  3. 一些老的构建系统或科学计算代码生成场景 在 C/C++ 高性能代码生成、模板展开时还可能见到 m4 脚本。

六、现代替代与比较 #

功能目标现代替代工具相比 m4 的优劣
配置生成 / 模板化Jinja2、Mustache、Go template更直观、语法现代
构建系统适配CMake、Meson、Bazel自动化程度高、跨平台更好
代码生成Python、Go、Rust 脚本拥有完整语言能力
轻量宏预处理cpp、mcpp更专注于 C/C++ 生态

**GNU m4 是一种通用宏展开器,用于在编译或配置前自动生成文本。**它是 80 年代 Unix 世界的“模板引擎”,在现代主要遗留在 autotools 体系中,但仍是一块重要的历史基石。

参考: https://segmentfault.com/a/1190000004104696

m4 有独特的“延迟展开机制”:

  • 直接写 NAME → 立即展开;
  • 用引号包围 `NAME’ → 阻止展开;
  • 嵌套宏时,要精确控制展开顺序。