将 Zig 作为 C 语言构建系统
即使你暂时不打算学习 Zig 语言本身,Zig 工具链也是一个非常优秀的 C/C++ 构建工具。它内置交叉编译支持、增量编译、智能缓存,且无需安装额外的工具链。
为什么用 Zig 构建 C 项目?
传统的 C 项目构建工具(Make、CMake、Autotools)各有痛点:
| 工具 | 问题 |
|---|---|
| Make | 语法晦涩,跨平台困难 |
| CMake | 配置复杂,学习曲线陡峭 |
| Autotools | 过于繁琐,现代项目很少使用 |
Zig 构建系统的优势:
- 零配置交叉编译:一行命令编译到任意平台
- 内置 C/C++ 编译器:基于 LLVM,无需安装 GCC/Clang
- 增量编译与缓存:只重新编译修改过的文件
- 声明式配置:使用 Zig 语言编写构建脚本,类型安全
- 单一可执行文件:整个工具链只有一个
zig二进制文件
项目结构
我们将构建一个简单的 C 项目,包含一个 power 函数用于计算幂次方:
helloCByzig/
├── build.zig # Zig 构建脚本
├── include/
│ └── foo.h # 头文件
└── src/
├── main.c # 主程序
└── foo.c # power 函数实现
源代码
include/foo.h
#ifndef FOO_H
#define FOO_H
// power: 计算 base 的 n 次方; n >= 0
int power(int base, int n);
#endif
src/foo.c
#include "foo.h"
int power(int base, int n) {
int i, p;
p = 1;
for (i = 1; i <= n; ++i)
p = p * base;
return p;
}
src/main.c
#include <stdio.h>
#include "foo.h"
int main() {
int i;
for (i = 0; i < 10; ++i)
printf("%d %d %d\n", i, power(2, i), power(-3, i));
return 0;
}
build.zig 详解
这是项目的核心——Zig 构建脚本:
const std = @import("std");
pub fn build(b: *std.Build) void {
// 标准目标选项(允许通过命令行指定目标平台)
const target = b.standardTargetOptions(.{});
// 标准优化选项(Debug/ReleaseSafe/ReleaseFast/ReleaseSmall)
const optimize = b.standardOptimizeOption(.{});
// 创建可执行文件
const exe = b.addExecutable(.{
.name = "hello",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true, // 链接 C 标准库
}),
});
// 添加 C 源文件
exe.addCSourceFiles(.{
.files = &.{
"src/main.c",
"src/foo.c",
},
.flags = &.{
"-Wall", // 开启所有警告
"-Wextra", // 开启额外警告
"-std=c11", // 使用 C11 标准
},
});
// 头文件搜索路径
exe.addIncludePath(b.path("include"));
// 安装构建产物
b.installArtifact(exe);
// 添加 run 命令
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
// 允许传递命令行参数
if (b.args) |args| {
run_cmd.addArgs(args);
}
// 创建 "zig build run" 步骤
const run_step = b.step("run", "Run the application");
run_step.dependOn(&run_cmd.step);
}
build.zig 关键概念
| 函数/方法 | 作用 |
|---|---|
b.standardTargetOptions(.{}) | 允许通过 -Dtarget=xxx 指定目标平台 |
b.standardOptimizeOption(.{}) | 允许通过 -Doptimize=xxx 指定优化级别 |
b.addExecutable() | 创建可执行文件构建目标 |
exe.addCSourceFiles() | 添加 C 源文件和编译标志 |
exe.addIncludePath() | 添加头文件搜索路径 |
b.installArtifact() | 将构建产物安装到 zig-out/bin/ |
b.addRunArtifact() | 创建运行命令 |
构建与运行
基本命令
# 构建项目
zig build
# 构建并运行
zig build run
# 查看构建产物
ls zig-out/bin/
# 输出: hello
运行结果
0 1 1
1 2 -3
2 4 9
3 8 -27
4 16 81
5 32 -243
6 64 729
7 128 -2187
8 256 6561
9 512 -19683
优化级别
Zig 提供四种优化级别:
# Debug(默认):包含调试信息,无优化
zig build
# ReleaseSafe:优化 + 运行时安全检查
zig build -Doptimize=ReleaseSafe
# ReleaseFast:最大性能优化
zig build -Doptimize=ReleaseFast
# ReleaseSmall:最小二进制体积
zig build -Doptimize=ReleaseSmall
交叉编译
Zig 的杀手级特性——零配置交叉编译:
# 编译到 Windows
zig build -Dtarget=x86_64-windows-gnu
# 编译到 ARM64 Linux(如树莓派 4)
zig build -Dtarget=aarch64-linux-gnu
# 编译到 macOS
zig build -Dtarget=x86_64-macos
# 编译到 macOS ARM(M1/M2)
zig build -Dtarget=aarch64-macos
# 编译到 WebAssembly
zig build -Dtarget=wasm32-wasi
无需安装任何交叉编译工具链,Zig 内置了所有目标平台的支持。
直接使用 zig cc
如果不想写 build.zig,可以直接使用 zig cc 作为 C 编译器的替代品:
# 编译单文件
zig cc -o hello src/main.c src/foo.c -Iinclude -Wall
# 运行
./hello
# 交叉编译到 Windows
zig cc -target x86_64-windows-gnu -o hello.exe src/main.c src/foo.c -Iinclude
zig cc 可以作为 gcc 或 clang 的直接替代品。
与 CMake 对比
同样的项目,CMake 的 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10)
project(hello C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
add_executable(hello
src/main.c
src/foo.c
)
target_include_directories(hello PRIVATE include)
构建步骤:
# CMake 需要两步
mkdir build && cd build
cmake ..
make
# Zig 只需要一步
zig build
交叉编译对比:
# CMake 交叉编译(需要安装工具链 + 编写工具链文件)
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain-arm.cmake ..
# Zig 交叉编译(零配置)
zig build -Dtarget=aarch64-linux-gnu
扩展:构建静态库
如果想将 power 函数编译为静态库:
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// 创建静态库
const lib = b.addStaticLibrary(.{
.name = "foo",
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
lib.addCSourceFiles(.{
.files = &.{"src/foo.c"},
.flags = &.{ "-Wall", "-std=c11" },
});
lib.addIncludePath(b.path("include"));
lib.installHeader(b.path("include/foo.h"), "foo.h");
b.installArtifact(lib);
}
构建后会生成 zig-out/lib/libfoo.a。
总结
Zig 作为 C 构建系统的优势:
| 特性 | 说明 |
|---|---|
| 单一工具 | 一个 zig 二进制文件包含编译器、链接器、构建系统 |
| 声明式配置 | 使用真正的编程语言(Zig)编写构建脚本 |
| 零配置交叉编译 | 内置所有目标平台支持 |
| 增量编译 | 智能缓存,只重编译修改的文件 |
| C/C++ 兼容 | 可以直接编译 C/C++ 代码 |
即使你不打算使用 Zig 语言本身,将 zig cc 和 zig build 作为 C 项目的构建工具也是一个明智的选择。