Ziglings 笔记 93: 拥抱 C 语言 (C Interop)

不是取代,是融合

在 Ziglings 的第 93 个练习中,我们终于见到了 Zig 取代 C 语言的策略:并不是把 C 语言扔进垃圾堆,而是完美地兼容它

通过内置的 @cImport,Zig 可以直接理解 C 的头文件。这意味着你可以直接在 Zig 项目中使用数以万计的现有 C 库(如 SDL, OpenGL, Openssl),而不需要编写繁琐的绑定(Bindings)。

挑战:Hello C

我们需要调用 C 标准库 unistd.h 中的 write 函数,向标准错误输出(stderr)打印一段文本。

解决方案

我们需要填写 write 函数的参数。

  • 文件描述符 2 代表 stderr
  • 字符串直接传入即可。
  • 长度需要我们手动数一下:“Hello C from Zig!” 正好是 17 个字符。
const std = @import("std");

// 核心:导入 C 库
// 这不仅仅是包含头文件,这是在调用内置的 C 编译器前端
const c = @cImport({
    @cInclude("unistd.h"); // 提供 write
    @cInclude("stdio.h");  // 提供 printf (本题未使用)
});

pub fn main() void {
    // 调用 C 函数 c.write
    // 参数 1: File Descriptor (2 = stderr)
    // 参数 2: Buffer (Zig 字符串自动转为 C 指针)
    // 参数 3: Length (17 bytes)
    const c_res = c.write(2, "Hello C from Zig!", 17);

    // 打印返回值 (ssize_t 在 Zig 中会被映射为相应的整数类型)
    std.debug.print(" - C result is {d} chars written.\n", .{c_res});
}

核心知识点总结

1. @cImport 的魔法

当你写下这段代码时,Zig 实际上是在后台运行 Clang 来解析 C 代码。它将 C 的类型系统映射到 Zig 的类型系统:

  • int -> c_int
  • char * -> [*c]u8 (C 指针)
  • struct -> extern struct

2. 编译标志 -lc

这是新手最容易踩的坑。 Zig 代码本身不需要 libc,但一旦你调用了 C 函数,就需要链接 C 运行库。 在运行或构建时,必须加上 -lc 参数:

zig run -lc 085_c_interop.zig

3. 指针的自动转换

C 函数通常需要 void*char*。Zig 的字符串字面量(*const [N:0]u8)和切片在传递给 C 函数时,会自动进行类型转换 (Coercion)。这让 Zig 写起来像脚本语言一样方便,但底层依然是类型安全的。


后续预告:我们已经打开了 C 语言的大门。既然能调用 C,那我们在 Zig 里如何处理 C 语言那种“以 0 结尾”的字符串呢?Zig 标准库提供了哪些工具?下一篇我们将探讨 C 字符串的处理。