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(¤t_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 的一个重要优势,为系统编程提供了极大的灵活性。
在下一章中,我们将学习关于未定义行为的概念。