Skip to content

Zig 内存管理教程

Zig 没有运行时内存管理,也没有默认分配器。程序员必须始终回答一个问题:

字节在哪里?

// Zig 不像 C 有默认的 malloc/free
// 所有需要分配内存的函数都接受 Allocator 参数
fn processData(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
const result = try allocator.alloc(u8, input.len);
@memcpy(result, input);
return result;
}
  • 无运行时开销:适合实时系统、内核、嵌入式设备
  • 完全控制:程序员决定内存的生命周期
  • 确定性行为:没有垃圾回收的暂停
  • 跨环境兼容:从微控制器到服务器
const std = @import("std");
test "使用分配器" {
// 1. 准备内存缓冲区
var buffer: [100]u8 = undefined;
// 2. 创建固定缓冲区分配器
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
// 3. 传递分配器给需要内存的函数
const result = try concat(allocator, "hello", "world");
defer allocator.free(result); // 记得释放!
try std.testing.expectEqualStrings("helloworld", result);
}
fn concat(allocator: std.mem.Allocator, a: []const u8, b: []const u8) ![]u8 {
// 分配内存
const result = try allocator.alloc(u8, a.len + b.len);
// 复制数据
@memcpy(result[0..a.len], a);
@memcpy(result[a.len..], b);
return result;
}
// 测试时方便使用的分配器(带泄漏检测)
test "testing allocator" {
const allocator = std.testing.allocator;
const data = try allocator.alloc(u8, 100);
defer allocator.free(data); // 必须释放,否则测试失败
// 使用数据...
}
你需要分配内存吗?
├─ 是 → 你是库作者吗?
│ ├─ 是 → 接受 Allocator 参数,让用户决定
│ └─ 否 → 你链接 libc 吗?
│ ├─ 是 → 使用 std.heap.c_allocator
│ └─ 否 → 最大内存需求在编译期已知吗?
│ ├─ 是 → 使用 FixedBufferAllocator
│ └─ 否 → 程序是命令行应用吗?
│ ├─ 是 → 使用 ArenaAllocator
│ └─ 否 → 有循环模式(如游戏主循环)吗?
│ ├─ 是 → 使用 ArenaAllocator(每周期清理)
│ └─ 否 → 需要通用分配器...
// 库函数应接受分配器参数
pub fn createBuffer(allocator: std.mem.Allocator, size: usize) ![]u8 {
return allocator.alloc(u8, size);
}
pub fn main() !void {
const allocator = std.heap.c_allocator;
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer);
}
test "fixed buffer allocator" {
// 预先分配栈内存
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
// 只能在这个缓冲区分配
const data = try allocator.alloc(u8, 500);
// 不需要手动释放
}
pub fn main() !void {
// 创建竞技场分配器
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // 一次释放所有内存
const allocator = arena.allocator();
// 任意分配,无需单独释放
const name = try allocator.dupe(u8, "Zig");
const numbers = try allocator.alloc(i32, 100);
const user = try allocator.create(User);
// 程序结束时自动清理所有内存
}

场景 5:循环模式(游戏/Web服务器)

Section titled “场景 5:循环模式(游戏/Web服务器)”
// 游戏主循环
fn gameLoop(allocator: std.mem.Allocator) void {
while (true) {
// 每帧创建新的竞技场
var frame_arena = std.heap.ArenaAllocator.init(allocator);
defer frame_arena.deinit();
const frame_allocator = frame_arena.allocator();
// 帧内所有分配
processFrame(frame_allocator);
// 帧结束,自动释放所有内存
}
}
test "allocation failure" {
// 创建总是失败的分配器
var failing_allocator = std.testing.FailingAllocator.init(
std.testing.allocator,
2 // 第2次分配失败
);
const allocator = failing_allocator.allocator();
// 第一次分配成功
_ = try allocator.alloc(u8, 10);
// 第二次分配应该失败
try std.testing.expectError(
error.OutOfMemory,
allocator.alloc(u8, 10)
);
}
const GeneralPurposeAllocator = std.heap.GeneralPurposeAllocator(.{});
pub fn main() !void {
var gpa = GeneralPurposeAllocator{};
defer _ = gpa.deinit(); // 检查泄漏
const allocator = gpa.allocator();
// 正常使用...
}
// "hello" 在全局常量数据区,不可修改
const str: []const u8 = "hello"; // ✅ 正确
// 错误:尝试修改字符串字面量
fn modifyString(s: []u8) void {}
modifyString("hello"); // ❌ 编译错误
// 错误信息:expected type '[]u8', found '*const [5:0]u8'
// 全局变量:全局数据区
var global_var: i32 = 42;
// 函数内变量:栈帧
fn example() void {
var local_var: i32 = 10; // 在栈上
const local_const: i32 = 20; // 可能在常量区
// 危险:返回局部变量指针
// return &local_var; // ❌ 悬垂指针!
}
// 结构体字段:跟随结构体
const Point = struct {
x: i32, // 存储位置取决于 Point 实例
y: i32,
};
// 编译期已知的值在常量区
const COMPILE_TIME_CONST: i32 = 100;
comptime {
const ct_var: i32 = 200; // 也在常量区
}

Zig 不选择崩溃,而是用 error.OutOfMemory 表示分配失败:

fn safeAllocate(allocator: std.mem.Allocator, size: usize) ![]u8 {
return allocator.alloc(u8, size) catch |err| {
std.debug.print("分配失败: {}\n", .{err});
return error.OutOfMemory;
};
}
// 使用时必须处理可能的失败
const data = safeAllocate(allocator, 1024) catch {
// 优雅处理:清理、记录、退出
std.debug.print("内存不足,退出程序\n", .{});
std.process.exit(1);
};
  1. 可移植性:不是所有系统都有内存过量使用
  2. 可重用性:库代码应在所有环境下工作
  3. 可靠性:防止系统锁死和 OOM Killer
  4. 嵌入式系统:内存有限且固定
// 当前 Zig 递归工作正常
fn factorial(n: u32) u32 {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// 但要注意:没有栈溢出保护(未来版本会添加)
test "recursion" {
// 深度递归可能导致栈溢出
// const result = factorial(100000); // 危险!
}
// 使用迭代代替深度递归
fn factorialIterative(n: u32) u32 {
var result: u32 = 1;
var i: u32 = 2;
while (i <= n) : (i += 1) {
result *= i;
}
return result;
}
// 尾递归优化(未来支持)
fn tailRecursive(n: u32, acc: u32) u32 {
if (n <= 1) return acc;
return tailRecursive(n - 1, n * acc);
}
// 明确所有权:文档说明谁负责释放
/// 调用者拥有返回的内存,必须调用 free 释放
pub fn createBuffer(allocator: Allocator, size: usize) ![]u8 {
return allocator.alloc(u8, size);
}
// 使用
const buffer = try createBuffer(allocator, 100);
defer allocator.free(buffer); // 调用者负责释放
const ArrayList = struct {
items: []T, // 生命周期:直到下次调整大小
allocator: Allocator,
pub fn append(self: *ArrayList, item: T) !void {
if (self.items.len == self.capacity) {
// 重新分配,旧 items 失效!
const new_items = try self.allocator.alloc(T, self.capacity * 2);
@memcpy(new_items[0..self.items.len], self.items);
self.allocator.free(self.items);
self.items = new_items;
}
// ...
}
};
// 模式1:创建者拥有
fn createUser(allocator: Allocator) !*User {
const user = try allocator.create(User);
user.* = .{ .name = "Alice", .age = 30 };
return user; // 调用者现在拥有
}
// 模式2:借出(借用)
fn processUser(user: *User) void {
// 只是借用,不获取所有权
user.age += 1;
}
// 模式3:共享所有权(需要引用计数)
const Shared = struct {
data: *Data,
ref_count: *usize,
pub fn clone(self: Shared) Shared {
self.ref_count.* += 1;
return self;
}
pub fn deinit(self: Shared, allocator: Allocator) void {
self.ref_count.* -= 1;
if (self.ref_count.* == 0) {
allocator.destroy(self.data);
allocator.free(self.ref_count);
}
}
};
const MemoryManager = struct {
// 策略1:单一所有者
pub fn singleOwner(allocator: Allocator) !void {
const data = try allocator.alloc(u8, 100);
defer allocator.free(data); // 明确释放点
// 使用 data...
}
// 策略2:竞技场模式
pub fn arenaPattern() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
// 任意分配,无需单独释放
}
// 策略3:池分配器
pub fn poolAllocator() void {
// 预分配对象池
var pool = std.heap.MemoryPool(Item).init(allocator);
defer pool.deinit();
const item1 = try pool.create();
const item2 = try pool.create();
// 自动返回池中
}
};
fn complexOperation(allocator: Allocator) !Result {
// 分配多个资源
const buffer1 = try allocator.alloc(u8, 100);
defer allocator.free(buffer1);
const buffer2 = try allocator.alloc(u8, 200);
errdefer allocator.free(buffer2); // 只在错误时清理
const connection = try connectToDatabase();
defer connection.close();
// 如果这里出错,buffer2 会自动释放
try processData(buffer1, buffer2, connection);
return .success;
}
// 使用栈分配小对象
fn processSmallData() void {
var stack_buffer: [1024]u8 = undefined; // 在栈上
// 使用 stack_buffer...
}
// 批量分配减少碎片
fn batchAllocation(allocator: Allocator) !void {
// 一次分配大块内存
const big_buffer = try allocator.alloc(u8, 1024 * 1024);
defer allocator.free(big_buffer);
// 手动管理大块中的小块
const slice1 = big_buffer[0..100];
const slice2 = big_buffer[100..200];
// ...
}
// 对齐考虑
fn alignedAllocation(allocator: Allocator) !void {
// 分配对齐的内存
const aligned = try allocator.alignedAlloc(u8, 64, 1024);
defer allocator.free(aligned);
}
const std = @import("std");
pub fn main() !void {
// 1. 选择分配器
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer {
const leaked = gpa.deinit();
if (leaked) std.debug.print("检测到内存泄漏!\n", .{});
}
const allocator = gpa.allocator();
// 2. 使用竞技场管理临时数据
var temp_arena = std.heap.ArenaAllocator.init(allocator);
defer temp_arena.deinit();
const temp_allocator = temp_arena.allocator();
// 3. 处理用户输入
const input = try readUserInput(temp_allocator);
// 4. 永久数据使用主分配器
const user = try createUser(allocator, input);
defer destroyUser(allocator, user);
// 5. 处理业务逻辑
try processUserData(allocator, user);
}
fn readUserInput(allocator: std.mem.Allocator) ![]const u8 {
const stdin = std.io.getStdIn().reader();
var buffer = std.ArrayList(u8).init(allocator);
defer buffer.deinit(); // 竞技场模式中可选
try stdin.streamUntilDelimiter(buffer.writer(), '\n', null);
return buffer.toOwnedSlice(); // 转移所有权
}

Zig 的内存管理要求程序员:

  1. 明确选择分配器:根据场景选择合适的内存管理策略
  2. 处理分配失败:不忽略 error.OutOfMemory
  3. 跟踪所有权:明确谁负责释放内存
  4. 理解生命周期:避免悬垂指针
  5. 合理选择存储位置:栈、堆、常量区的正确使用

通过遵循这些原则,你可以构建出高效、可靠、可移植的 Zig 程序。记住核心问题:字节在哪里? 这个问题贯穿 Zig 内存管理的始终。