Skip to content

Zig 与 C 交互

Zig 设计时就考虑了与 C 语言的无缝互操作性。本章将详细介绍如何在 Zig 中使用 C 库、调用 C 函数以及将 Zig 代码暴露给 C。

C 互操作基础

导入 C 头文件

Zig 可以直接导入和使用 C 头文件:

zig
const std = @import("std");
const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("stdlib.h");
    @cInclude("string.h");
    @cInclude("math.h");
});

pub fn main() void {
    // 使用 C 标准库函数
    _ = c.printf("Hello from C printf!\n");
    
    // 使用 C 数学函数
    const x: f64 = 3.14159;
    const sin_x = c.sin(x);
    const cos_x = c.cos(x);
    
    _ = c.printf("sin(%.5f) = %.5f\n", x, sin_x);
    _ = c.printf("cos(%.5f) = %.5f\n", x, cos_x);
    
    // 使用 C 字符串函数
    var buffer: [100]u8 = undefined;
    _ = c.strcpy(&buffer, "Hello, C World!");
    _ = c.printf("String: %s\n", &buffer);
    _ = c.printf("Length: %zu\n", c.strlen(&buffer));
}

C 类型映射

Zig 提供了与 C 类型对应的类型:

zig
const std = @import("std");
const c = @cImport({
    @cInclude("stdint.h");
});

pub fn main() void {
    // C 基本类型
    var c_int: c_int = 42;
    var c_uint: c_uint = 42;
    var c_long: c_long = 1000000;
    var c_float: f32 = 3.14;
    var c_double: f64 = 3.141592653589793;
    
    // C 字符类型
    var c_char: u8 = 'A';
    
    // C 指针类型
    var c_ptr: [*c]u8 = null;
    
    std.debug.print("C 类型映射示例:\n");
    std.debug.print("c_int: {} (大小: {} 字节)\n", .{ c_int, @sizeOf(c_int) });
    std.debug.print("c_uint: {} (大小: {} 字节)\n", .{ c_uint, @sizeOf(c_uint) });
    std.debug.print("c_long: {} (大小: {} 字节)\n", .{ c_long, @sizeOf(c_long) });
    std.debug.print("c_float: {d:.2} (大小: {} 字节)\n", .{ c_float, @sizeOf(f32) });
    std.debug.print("c_double: {d:.10} (大小: {} 字节)\n", .{ c_double, @sizeOf(f64) });
    std.debug.print("c_char: {c} (值: {})\n", .{ c_char, c_char });
    std.debug.print("c_ptr: {?*}\n", .{c_ptr});
}

调用 C 函数

使用 C 标准库

zig
const std = @import("std");
const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("stdlib.h");
    @cInclude("time.h");
});

pub fn main() void {
    // 内存分配
    const size = 10;
    const ptr = c.malloc(size * @sizeOf(c_int));
    defer c.free(ptr);
    
    if (ptr == null) {
        std.debug.print("内存分配失败\n", .{});
        return;
    }
    
    // 将指针转换为适当的类型
    const int_array: [*c]c_int = @ptrCast(@alignCast(ptr));
    
    // 初始化数组
    for (0..size) |i| {
        int_array[i] = @intCast(i * i);
    }
    
    // 打印数组
    _ = c.printf("C 分配的数组:\n");
    for (0..size) |i| {
        _ = c.printf("array[%zu] = %d\n", i, int_array[i]);
    }
    
    // 使用 C 时间函数
    const current_time = c.time(null);
    const time_str = c.ctime(&current_time);
    _ = c.printf("当前时间: %s", time_str);
}

文件操作示例

zig
const std = @import("std");
const c = @cImport({
    @cInclude("stdio.h");
});

pub fn main() void {
    const filename = "c_test.txt";
    const content = "Hello from Zig using C FILE operations!\n";
    
    // 写入文件
    const write_file = c.fopen(filename, "w");
    if (write_file == null) {
        std.debug.print("无法打开文件进行写入\n", .{});
        return;
    }
    defer _ = c.fclose(write_file);
    
    _ = c.fprintf(write_file, "%s", content);
    _ = c.fflush(write_file);
    
    std.debug.print("文件写入完成\n", .{});
    
    // 读取文件
    const read_file = c.fopen(filename, "r");
    if (read_file == null) {
        std.debug.print("无法打开文件进行读取\n", .{});
        return;
    }
    defer _ = c.fclose(read_file);
    
    var buffer: [256]u8 = undefined;
    if (c.fgets(&buffer, buffer.len, read_file) != null) {
        std.debug.print("从文件读取: {s}", .{@as([*:0]u8, @ptrCast(&buffer))});
    }
    
    // 清理文件
    _ = c.remove(filename);
}

使用第三方 C 库

创建构建脚本

首先创建一个 build.zig 文件来链接 C 库:

zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    
    const exe = b.addExecutable(.{
        .name = "c-interop-example",
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });
    
    // 链接 C 标准库
    exe.linkLibC();
    
    // 链接数学库 (Unix 系统)
    if (target.result.os.tag != .windows) {
        exe.linkSystemLibrary("m");
    }
    
    // 添加包含路径
    exe.addIncludePath(.{ .path = "include" });
    
    // 添加 C 源文件
    exe.addCSourceFile(.{
        .file = .{ .path = "src/helper.c" },
        .flags = &[_][]const u8{"-std=c99"},
    });
    
    b.installArtifact(exe);
    
    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());
    
    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

C 辅助函数

创建 src/helper.c

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 简单的数学运算
int add_numbers(int a, int b) {
    return a + b;
}

double calculate_circle_area(double radius) {
    return 3.14159265359 * radius * radius;
}

// 字符串处理
char* create_greeting(const char* name) {
    size_t len = strlen("Hello, ") + strlen(name) + strlen("!") + 1;
    char* greeting = malloc(len);
    if (greeting) {
        strcpy(greeting, "Hello, ");
        strcat(greeting, name);
        strcat(greeting, "!");
    }
    return greeting;
}

void free_string(char* str) {
    free(str);
}

// 数组处理
void process_array(int* array, size_t size) {
    for (size_t i = 0; i < size; i++) {
        array[i] *= 2;
    }
}

// 结构体操作
typedef struct {
    int x;
    int y;
} Point;

Point create_point(int x, int y) {
    Point p = {x, y};
    return p;
}

double point_distance(Point a, Point b) {
    int dx = a.x - b.x;
    int dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

在 Zig 中使用 C 库

创建 src/main.zig

zig
const std = @import("std");
const c = @cImport({
    @cInclude("helper.h"); // 假设有对应的头文件
    @cInclude("stdio.h");
    @cInclude("stdlib.h");
    @cInclude("math.h");
});

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    std.debug.print("使用 C 库示例\n");
    
    // 调用 C 数学函数
    const result = c.add_numbers(10, 20);
    std.debug.print("10 + 20 = {}\n", .{result});
    
    const area = c.calculate_circle_area(5.0);
    std.debug.print("半径为 5 的圆面积: {d:.2}\n", .{area});
    
    // 调用 C 字符串函数
    const name = "Zig Developer";
    const greeting = c.create_greeting(name);
    defer c.free_string(greeting);
    
    if (greeting != null) {
        const zig_greeting = std.mem.span(@as([*:0]u8, @ptrCast(greeting)));
        std.debug.print("问候语: {s}\n", .{zig_greeting});
    }
    
    // 数组处理
    var numbers = [_]c_int{ 1, 2, 3, 4, 5 };
    std.debug.print("处理前的数组: ");
    for (numbers) |num| {
        std.debug.print("{} ", .{num});
    }
    std.debug.print("\n");
    
    c.process_array(&numbers, numbers.len);
    
    std.debug.print("处理后的数组: ");
    for (numbers) |num| {
        std.debug.print("{} ", .{num});
    }
    std.debug.print("\n");
    
    // 结构体操作
    const point1 = c.create_point(0, 0);
    const point2 = c.create_point(3, 4);
    const distance = c.point_distance(point1, point2);
    
    std.debug.print("点 ({}, {}) 到点 ({}, {}) 的距离: {d:.2}\n", 
                    .{ point1.x, point1.y, point2.x, point2.y, distance });
}

将 Zig 函数暴露给 C

导出 Zig 函数

zig
const std = @import("std");

// 导出简单函数
export fn zig_add(a: c_int, b: c_int) c_int {
    return a + b;
}

export fn zig_multiply(a: c_double, b: c_double) c_double {
    return a * b;
}

// 导出字符串处理函数
export fn zig_string_length(str: [*:0]const u8) c_size_t {
    return std.mem.len(str);
}

// 导出数组处理函数
export fn zig_sum_array(array: [*]const c_int, size: c_size_t) c_int {
    var sum: c_int = 0;
    for (0..size) |i| {
        sum += array[i];
    }
    return sum;
}

// 导出结构体操作
const Point = extern struct {
    x: c_int,
    y: c_int,
};

export fn zig_create_point(x: c_int, y: c_int) Point {
    return Point{ .x = x, .y = y };
}

export fn zig_point_distance_squared(p1: Point, p2: Point) c_int {
    const dx = p1.x - p2.x;
    const dy = p1.y - p2.y;
    return dx * dx + dy * dy;
}

// 导出内存分配函数
export fn zig_allocate_array(size: c_size_t) ?[*]c_int {
    const allocator = std.heap.c_allocator;
    const array = allocator.alloc(c_int, size) catch return null;
    return array.ptr;
}

export fn zig_free_array(ptr: [*]c_int, size: c_size_t) void {
    const allocator = std.heap.c_allocator;
    const slice = ptr[0..size];
    allocator.free(slice);
}

// 测试函数
pub fn main() void {
    std.debug.print("Zig 函数导出示例\n");
    
    // 测试导出的函数
    const sum = zig_add(10, 20);
    std.debug.print("zig_add(10, 20) = {}\n", .{sum});
    
    const product = zig_multiply(3.14, 2.0);
    std.debug.print("zig_multiply(3.14, 2.0) = {d:.2}\n", .{product});
    
    const test_string = "Hello, World!";
    const length = zig_string_length(test_string);
    std.debug.print("zig_string_length(\"{s}\") = {}\n", .{ test_string, length });
    
    const test_array = [_]c_int{ 1, 2, 3, 4, 5 };
    const array_sum = zig_sum_array(&test_array, test_array.len);
    std.debug.print("zig_sum_array([1,2,3,4,5]) = {}\n", .{array_sum});
    
    const p1 = zig_create_point(0, 0);
    const p2 = zig_create_point(3, 4);
    const dist_sq = zig_point_distance_squared(p1, p2);
    std.debug.print("距离平方: {}\n", .{dist_sq});
}

生成 C 头文件

可以为导出的 Zig 函数生成 C 头文件:

c
// zig_exports.h
#ifndef ZIG_EXPORTS_H
#define ZIG_EXPORTS_H

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

// 基本函数
int zig_add(int a, int b);
double zig_multiply(double a, double b);

// 字符串函数
size_t zig_string_length(const char* str);

// 数组函数
int zig_sum_array(const int* array, size_t size);

// 结构体定义
typedef struct {
    int x;
    int y;
} Point;

// 结构体函数
Point zig_create_point(int x, int y);
int zig_point_distance_squared(Point p1, Point p2);

// 内存管理函数
int* zig_allocate_array(size_t size);
void zig_free_array(int* ptr, size_t size);

#ifdef __cplusplus
}
#endif

#endif // ZIG_EXPORTS_H

处理 C 字符串

字符串转换

zig
const std = @import("std");
const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("stdlib.h");
    @cInclude("string.h");
});

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // Zig 字符串转 C 字符串
    const zig_string = "Hello from Zig!";
    const c_string = try allocator.dupeZ(u8, zig_string);
    defer allocator.free(c_string);
    
    _ = c.printf("C 字符串: %s\n", c_string.ptr);
    
    // C 字符串转 Zig 字符串
    const c_literal = "Hello from C!";
    const zig_from_c = std.mem.span(@as([*:0]const u8, @ptrCast(c_literal)));
    std.debug.print("Zig 字符串: {s}\n", .{zig_from_c});
    
    // 使用 C 字符串函数
    var buffer: [100]u8 = undefined;
    _ = c.strcpy(&buffer, "Initial string");
    _ = c.strcat(&buffer, " + appended");
    
    const final_string = std.mem.span(@as([*:0]u8, @ptrCast(&buffer)));
    std.debug.print("拼接后的字符串: {s}\n", .{final_string});
    
    // 字符串比较
    const str1 = "apple";
    const str2 = "banana";
    const c_str1 = try allocator.dupeZ(u8, str1);
    const c_str2 = try allocator.dupeZ(u8, str2);
    defer allocator.free(c_str1);
    defer allocator.free(c_str2);
    
    const cmp_result = c.strcmp(c_str1.ptr, c_str2.ptr);
    std.debug.print("strcmp(\"{s}\", \"{s}\") = {}\n", .{ str1, str2, cmp_result });
}

错误处理和调试

C 错误处理

zig
const std = @import("std");
const c = @cImport({
    @cInclude("stdio.h");
    @cInclude("errno.h");
    @cInclude("string.h");
});

pub fn main() void {
    // 尝试打开不存在的文件
    const filename = "nonexistent_file.txt";
    const file = c.fopen(filename, "r");
    
    if (file == null) {
        const errno_val = c.__errno_location().*;  // Linux
        // const errno_val = c._errno();  // Windows
        const error_msg = c.strerror(errno_val);
        
        std.debug.print("文件打开失败: {s}\n", .{filename});
        std.debug.print("错误代码: {}\n", .{errno_val});
        std.debug.print("错误信息: {s}\n", .{std.mem.span(@as([*:0]u8, @ptrCast(error_msg)))});
    } else {
        _ = c.fclose(file);
        std.debug.print("文件打开成功\n", .{});
    }
    
    // 内存分配失败处理
    const huge_size: usize = std.math.maxInt(usize);
    const ptr = c.malloc(huge_size);
    
    if (ptr == null) {
        std.debug.print("内存分配失败: 请求大小太大\n", .{});
    } else {
        c.free(ptr);
        std.debug.print("内存分配成功\n", .{});
    }
}

性能考虑

调用开销

zig
const std = @import("std");
const c = @cImport({
    @cInclude("math.h");
    @cInclude("time.h");
});

// Zig 实现的平方根
fn zig_sqrt(x: f64) f64 {
    return @sqrt(x);
}

pub fn main() void {
    const iterations = 1000000;
    const test_value: f64 = 2.0;
    
    // 测试 C 函数调用
    const c_start = c.clock();
    for (0..iterations) |_| {
        _ = c.sqrt(test_value);
    }
    const c_end = c.clock();
    const c_time = @as(f64, @floatFromInt(c_end - c_start)) / c.CLOCKS_PER_SEC;
    
    // 测试 Zig 内置函数
    const zig_start = c.clock();
    for (0..iterations) |_| {
        _ = zig_sqrt(test_value);
    }
    const zig_end = c.clock();
    const zig_time = @as(f64, @floatFromInt(zig_end - zig_start)) / c.CLOCKS_PER_SEC;
    
    std.debug.print("性能比较 ({} 次迭代):\n", .{iterations});
    std.debug.print("C sqrt(): {d:.6} 秒\n", .{c_time});
    std.debug.print("Zig @sqrt(): {d:.6} 秒\n", .{zig_time});
    std.debug.print("性能比: {d:.2}x\n", .{c_time / zig_time});
}

最佳实践

1. 类型安全

zig
const std = @import("std");
const c = @cImport({
    @cInclude("stdio.h");
});

// ✅ 使用类型安全的包装器
fn safe_printf(comptime fmt: []const u8, args: anytype) void {
    const c_fmt = fmt ++ "\x00"; // 确保以 null 结尾
    _ = @call(.auto, c.printf, .{c_fmt.ptr} ++ args);
}

// ✅ 检查 C 函数返回值
fn safe_fopen(filename: []const u8, mode: []const u8, allocator: std.mem.Allocator) !?*c.FILE {
    const c_filename = try allocator.dupeZ(u8, filename);
    defer allocator.free(c_filename);
    
    const c_mode = try allocator.dupeZ(u8, mode);
    defer allocator.free(c_mode);
    
    const file = c.fopen(c_filename.ptr, c_mode.ptr);
    return file;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // 使用类型安全的包装器
    safe_printf("安全的 printf: %d %s", .{ 42, "test" });
    
    // 安全的文件操作
    if (try safe_fopen("test.txt", "w", allocator)) |file| {
        defer _ = c.fclose(file);
        _ = c.fprintf(file, "Hello, safe C interop!\n");
    } else {
        std.debug.print("文件打开失败\n", .{});
    }
}

2. 内存管理

zig
const std = @import("std");
const c = @cImport({
    @cInclude("stdlib.h");
});

// ✅ RAII 风格的 C 内存管理
const CBuffer = struct {
    ptr: [*]u8,
    size: usize,
    
    const Self = @This();
    
    pub fn init(size: usize) !Self {
        const ptr = c.malloc(size);
        if (ptr == null) return error.OutOfMemory;
        
        return Self{
            .ptr = @ptrCast(ptr),
            .size = size,
        };
    }
    
    pub fn deinit(self: *Self) void {
        c.free(self.ptr);
        self.ptr = undefined;
        self.size = 0;
    }
    
    pub fn slice(self: *const Self) []u8 {
        return self.ptr[0..self.size];
    }
};

pub fn main() !void {
    var buffer = try CBuffer.init(1024);
    defer buffer.deinit();
    
    const data = buffer.slice();
    @memset(data, 0);
    
    std.debug.print("C 缓冲区大小: {} 字节\n", .{data.len});
}

总结

本章详细介绍了 Zig 与 C 语言的互操作:

  • ✅ 导入和使用 C 头文件
  • ✅ C 类型映射和函数调用
  • ✅ 使用第三方 C 库
  • ✅ 将 Zig 函数导出给 C
  • ✅ C 字符串处理
  • ✅ 错误处理和调试
  • ✅ 性能考虑和最佳实践

Zig 与 C 的无缝互操作性使得开发者可以逐步迁移现有的 C 代码库,或者在 Zig 项目中使用成熟的 C 库。这种互操作性是 Zig 的一个重要优势,为系统编程提供了极大的灵活性。

在下一章中,我们将学习关于未定义行为的概念。

本站内容仅供学习和研究使用。