zig-memory

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Zig Memory Management Guide

Zig内存管理指南

Core Principle: Every allocation must have a corresponding deallocation. Use
defer
for normal cleanup,
errdefer
for error path cleanup.
This skill ensures safe memory management in Zig, preventing memory leaks and use-after-free bugs.
Official Documentation:
Related Skills:
  • zig-0.15
    : API changes including ArrayList allocator parameter
  • solana-sdk-zig
    : Solana-specific memory constraints (32KB heap)
核心原则:每一次内存分配都必须有对应的释放操作。 使用
defer
处理常规清理,使用
errdefer
处理错误路径的清理。
本技能可确保Zig中的内存管理安全,避免内存泄漏和释放后使用的bug。
官方文档:
相关技能:
  • zig-0.15
    :包含ArrayList分配器参数在内的API变更
  • solana-sdk-zig
    :Solana特定的内存限制(32KB堆内存)

References

参考资料

Detailed allocator patterns and examples:
DocumentPathContent
Allocator Patterns
references/allocator-patterns.md
GPA, Arena, FixedBuffer, Testing allocators, BPF allocator
详细的分配器模式及示例:
文档路径内容
分配器模式
references/allocator-patterns.md
GPA、Arena、FixedBuffer、测试用分配器、BPF分配器

Resource Cleanup Pattern (Critical)

资源清理模式(关键)

Always Use defer for Cleanup

始终使用defer进行清理

zig
// ❌ WRONG - No cleanup
fn process(allocator: Allocator) !void {
    const buffer = try allocator.alloc(u8, 1024);
    // ... use buffer ...
    // Memory leaked!
}

// ✅ CORRECT - Immediate defer
fn process(allocator: Allocator) !void {
    const buffer = try allocator.alloc(u8, 1024);
    defer allocator.free(buffer);  // Always freed
    // ... use buffer ...
}
zig
// ❌ 错误 - 未做清理
fn process(allocator: Allocator) !void {
    const buffer = try allocator.alloc(u8, 1024);
    // ... 使用buffer ...
    // 内存泄漏!
}

// ✅ 正确 - 立即使用defer
fn process(allocator: Allocator) !void {
    const buffer = try allocator.alloc(u8, 1024);
    defer allocator.free(buffer);  // 一定会被释放
    // ... 使用buffer ...
}

Use errdefer for Error Path Cleanup

使用errdefer处理错误路径的清理

zig
// ❌ WRONG - Leak on error
fn createResource(allocator: Allocator) !*Resource {
    const res = try allocator.create(Resource);
    res.data = try allocator.alloc(u8, 100);  // If this fails, res leaks!
    try res.initialize();  // If this fails, both leak!
    return res;
}

// ✅ CORRECT - errdefer for each allocation
fn createResource(allocator: Allocator) !*Resource {
    const res = try allocator.create(Resource);
    errdefer allocator.destroy(res);  // Freed only on error

    res.data = try allocator.alloc(u8, 100);
    errdefer allocator.free(res.data);  // Freed only on error

    try res.initialize();  // If this fails, errdefers run
    return res;  // Success - errdefers don't run
}
zig
// ❌ 错误 - 发生错误时泄漏内存
fn createResource(allocator: Allocator) !*Resource {
    const res = try allocator.create(Resource);
    res.data = try allocator.alloc(u8, 100);  // 如果这一步失败,res会泄漏!
    try res.initialize();  // 如果这一步失败,两者都会泄漏!
    return res;
}

// ✅ 正确 - 为每一次分配使用errdefer
fn createResource(allocator: Allocator) !*Resource {
    const res = try allocator.create(Resource);
    errdefer allocator.destroy(res);  // 仅在错误时释放

    res.data = try allocator.alloc(u8, 100);
    errdefer allocator.free(res.data);  // 仅在错误时释放

    try res.initialize();  // 如果失败,errdefer会执行
    return res;  // 成功 - errdefer不会执行
}

ArrayList Memory Management (Zig 0.15+)

ArrayList内存管理(Zig 0.15+)

Critical: In Zig 0.15, ArrayList methods require explicit allocator:
zig
// ❌ WRONG (0.13/0.14 style)
var list = std.ArrayList(T).init(allocator);
defer list.deinit();
try list.append(item);

// ✅ CORRECT (0.15+ style)
var list = try std.ArrayList(T).initCapacity(allocator, 16);
defer list.deinit(allocator);  // Allocator required!
try list.append(allocator, item);  // Allocator required!
try list.appendSlice(allocator, items);
try list.ensureTotalCapacity(allocator, n);
const owned = try list.toOwnedSlice(allocator);
defer allocator.free(owned);  // Caller owns the slice
关键提示:在Zig 0.15中,ArrayList方法需要显式传入分配器:
zig
// ❌ 错误(0.13/0.14风格)
var list = std.ArrayList(T).init(allocator);
defer list.deinit();
try list.append(item);

// ✅ 正确(0.15+风格)
var list = try std.ArrayList(T).initCapacity(allocator, 16);
defer list.deinit(allocator);  // 必须传入分配器!
try list.append(allocator, item);  // 必须传入分配器!
try list.appendSlice(allocator, items);
try list.ensureTotalCapacity(allocator, n);
const owned = try list.toOwnedSlice(allocator);
defer allocator.free(owned);  // 调用者拥有该切片的所有权

ArrayList Method Reference (0.15+)

ArrayList方法参考(0.15+)

MethodAllocator?Notes
initCapacity(alloc, n)
YesPreferred initialization
deinit(alloc)
YesChanged in 0.15!
append(alloc, item)
YesChanged in 0.15!
appendSlice(alloc, items)
YesChanged in 0.15!
addOne(alloc)
YesReturns pointer to new slot
ensureTotalCapacity(alloc, n)
YesPre-allocate capacity
toOwnedSlice(alloc)
YesCaller must free result
appendAssumeCapacity(item)
NoAssumes capacity exists
items
field
NoRead-only access
方法是否需要分配器说明
initCapacity(alloc, n)
推荐的初始化方式
deinit(alloc)
0.15版本变更!
append(alloc, item)
0.15版本变更!
appendSlice(alloc, items)
0.15版本变更!
addOne(alloc)
返回新插槽的指针
ensureTotalCapacity(alloc, n)
预分配容量
toOwnedSlice(alloc)
调用者必须释放返回结果
appendAssumeCapacity(item)
假设容量已存在
items
字段
只读访问

HashMap Memory Management

HashMap内存管理

Managed HashMap (Recommended)

托管式HashMap(推荐)

zig
// Managed - stores allocator internally
var map = std.StringHashMap(V).init(allocator);
defer map.deinit();  // No allocator needed
try map.put(key, value);  // No allocator needed
zig
// 托管式 - 内部存储分配器
var map = std.StringHashMap(V).init(allocator);
defer map.deinit();  // 无需传入分配器
try map.put(key, value);  // 无需传入分配器

Unmanaged HashMap

非托管式HashMap

zig
// Unmanaged - requires allocator for each operation
var umap = std.StringHashMapUnmanaged(V){};
defer umap.deinit(allocator);  // Allocator required
try umap.put(allocator, key, value);  // Allocator required
zig
// 非托管式 - 每次操作都需要分配器
var umap = std.StringHashMapUnmanaged(V){};
defer umap.deinit(allocator);  // 必须传入分配器
try umap.put(allocator, key, value);  // 必须传入分配器

Which to Use?

如何选择?

TypeWhen to Use
Managed (
StringHashMap
)
General use, simpler API
Unmanaged (
StringHashMapUnmanaged
)
When allocator changes, performance-critical
类型使用场景
托管式(
StringHashMap
通用场景,API更简洁
非托管式(
StringHashMapUnmanaged
分配器会变更的场景、性能关键场景

Arena Allocator

Arena Allocator

Best for batch allocations freed together:
zig
// Arena - single deallocation frees everything
var arena = std.heap.ArenaAllocator.init(backing_allocator);
defer arena.deinit();  // Frees ALL allocations

const temp = arena.allocator();
const str1 = try temp.alloc(u8, 100);  // No individual free needed
const str2 = try temp.alloc(u8, 200);  // No individual free needed
// arena.deinit() frees both
最适合批量分配后统一释放的场景:
zig
// Arena - 一次释放操作即可释放所有分配的内存
var arena = std.heap.ArenaAllocator.init(backing_allocator);
defer arena.deinit();  // 释放所有分配的内存

const temp = arena.allocator();
const str1 = try temp.alloc(u8, 100);  // 无需单独释放
const str2 = try temp.alloc(u8, 200);  // 无需单独释放
// arena.deinit()会同时释放两者

Arena Use Cases

Arena使用场景

Use CaseWhy Arena
Temporary computationsFree all at once
Request handlingAllocate per request, free at end
ParsingAllocate AST nodes, free when done
Building stringsAccumulate, then transfer ownership
使用场景选择Arena的原因
临时计算一次性释放所有内存
请求处理为每个请求分配内存,请求结束时释放
解析操作分配AST节点,完成后释放
字符串构建累积内容,然后转移所有权

Testing Allocator (Leak Detection)

测试用分配器(泄漏检测)

std.testing.allocator
automatically detects memory leaks:
zig
test "no memory leak" {
    const allocator = std.testing.allocator;

    // If you forget to free, test FAILS with:
    // "memory address 0x... was never freed"
    const buffer = try allocator.alloc(u8, 100);
    defer allocator.free(buffer);  // MUST have this

    // Test code...
}
std.testing.allocator
会自动检测内存泄漏:
zig
test "no memory leak" {
    const allocator = std.testing.allocator;

    // 如果忘记释放,测试会失败并提示:
    // "memory address 0x... was never freed"
    const buffer = try allocator.alloc(u8, 100);
    defer allocator.free(buffer);  // 必须添加这一行

    // 测试代码...
}

Common Test Memory Issues

常见测试内存问题

zig
// ❌ WRONG - Memory leak
test "leaky test" {
    const allocator = std.testing.allocator;
    const data = try allocator.alloc(u8, 100);
    // Forgot free → test fails: "memory leak detected"
}

// ✅ CORRECT - Proper cleanup
test "clean test" {
    const allocator = std.testing.allocator;
    const data = try allocator.alloc(u8, 100);
    defer allocator.free(data);
    // Test code...
}

// ❌ WRONG - ArrayList leak
test "leaky arraylist" {
    const allocator = std.testing.allocator;
    var list = try std.ArrayList(u8).initCapacity(allocator, 16);
    // Forgot deinit → memory leak
}

// ✅ CORRECT - ArrayList cleanup
test "clean arraylist" {
    const allocator = std.testing.allocator;
    var list = try std.ArrayList(u8).initCapacity(allocator, 16);
    defer list.deinit(allocator);
    // Test code...
}
zig
// ❌ 错误 - 内存泄漏
test "leaky test" {
    const allocator = std.testing.allocator;
    const data = try allocator.alloc(u8, 100);
    // 忘记释放 → 测试失败:"memory leak detected"
}

// ✅ 正确 - 正确清理
test "clean test" {
    const allocator = std.testing.allocator;
    const data = try allocator.alloc(u8, 100);
    defer allocator.free(data);
    // 测试代码...
}

// ❌ 错误 - ArrayList泄漏
test "leaky arraylist" {
    const allocator = std.testing.allocator;
    var list = try std.ArrayList(u8).initCapacity(allocator, 16);
    // 忘记deinit → 内存泄漏
}

// ✅ 正确 - ArrayList清理
test "clean arraylist" {
    const allocator = std.testing.allocator;
    var list = try std.ArrayList(u8).initCapacity(allocator, 16);
    defer list.deinit(allocator);
    // 测试代码...
}

Segfault Prevention

段错误预防

Null Pointer Dereference

空指针解引用

zig
// ❌ DANGEROUS - Segfault
var ptr: ?*u8 = null;
_ = ptr.?.*;  // Dereference null → crash

// ✅ SAFE - Check null
var ptr: ?*u8 = null;
if (ptr) |p| {
    _ = p.*;
}
zig
// ❌ 危险 - 段错误
var ptr: ?*u8 = null;
_ = ptr.?.*;  // 解引用空指针 → 崩溃

// ✅ 安全 - 检查空指针
var ptr: ?*u8 = null;
if (ptr) |p| {
    _ = p.*;
}

Array Bounds

数组越界

zig
// ❌ DANGEROUS - Out of bounds
const arr = [_]u8{ 1, 2, 3 };
_ = arr[5];  // Index 5 > len 3 → undefined behavior

// ✅ SAFE - Bounds check
const arr = [_]u8{ 1, 2, 3 };
if (5 < arr.len) {
    _ = arr[5];
}
zig
// ❌ 危险 - 越界访问
const arr = [_]u8{ 1, 2, 3 };
_ = arr[5];  // 索引5超过长度3 → 未定义行为

// ✅ 安全 - 边界检查
const arr = [_]u8{ 1, 2, 3 };
if (5 < arr.len) {
    _ = arr[5];
}

Use After Free

释放后使用

zig
// ❌ DANGEROUS - Use after free
const data = try allocator.alloc(u8, 100);
allocator.free(data);
data[0] = 42;  // Use after free → undefined behavior

// ✅ SAFE - Set to undefined after free
const data = try allocator.alloc(u8, 100);
allocator.free(data);
// Don't use data after this point
zig
// ❌ 危险 - 释放后使用
const data = try allocator.alloc(u8, 100);
allocator.free(data);
data[0] = 42;  // 释放后使用 → 未定义行为

// ✅ 安全 - 释放后不再使用
const data = try allocator.alloc(u8, 100);
allocator.free(data);
// 释放后不要使用该数据

String Ownership

字符串所有权

Borrowed (Read-Only)

借用(只读)

zig
// Borrowed - caller keeps ownership
fn process(borrowed: []const u8) void {
    // Read-only, cannot modify, cannot free
    std.debug.print("{s}\n", .{borrowed});
}
zig
// 借用 - 调用者保留所有权
fn process(borrowed: []const u8) void {
    // 只读,不可修改,不可释放
    std.debug.print("{s}\n", .{borrowed});
}

Owned (Caller Must Free)

拥有(调用者必须释放)

zig
// Owned - caller takes ownership and must free
fn createMessage(allocator: Allocator, name: []const u8) ![]u8 {
    return try std.fmt.allocPrint(allocator, "Hello, {s}!", .{name});
}

// Usage
const msg = try createMessage(allocator, "World");
defer allocator.free(msg);  // Caller frees
zig
// 拥有 - 调用者获取所有权并必须释放
fn createMessage(allocator: Allocator, name: []const u8) ![]u8 {
    return try std.fmt.allocPrint(allocator, "Hello, {s}!", .{name});
}

// 使用示例
const msg = try createMessage(allocator, "World");
defer allocator.free(msg);  // 调用者负责释放

Solana BPF Allocator

Solana BPF分配器

In Solana programs, use the BPF bump allocator:
zig
const allocator = @import("solana_program_sdk").allocator.bpf_allocator;

// Limited to 32KB heap
const data = try allocator.alloc(u8, 1024);
// Note: BPF allocator does NOT support free()!
在Solana程序中,使用BPF bump分配器:
zig
const allocator = @import("solana_program_sdk").allocator.bpf_allocator;

// 堆内存限制为32KB
const data = try allocator.alloc(u8, 1024);
// 注意:BPF分配器不支持free()!

BPF Memory Constraints

BPF内存限制

ConstraintValue
Total heap32KB
Free support❌ None
Stack size64KB (with 4KB frame limit)
限制数值
总堆内存32KB
释放支持❌ 无
栈大小64KB(单帧限制4KB)

BPF Memory Tips

BPF内存使用技巧

  • Pre-calculate sizes when possible
  • Use stack for small/fixed allocations
  • Reuse buffers instead of reallocating
  • Use
    extern struct
    for zero-copy parsing
  • 尽可能预先计算大小
  • 小型/固定大小的分配使用栈内存
  • 复用缓冲区而非重新分配
  • 使用
    extern struct
    进行零拷贝解析

Common Error Messages

常见错误信息

ErrorCauseFix
memory leak detected
Forgot to freeAdd
defer allocator.free(...)
expected 2 arguments, found 1
ArrayList missing allocatorAdd allocator to
append
,
deinit
use of undefined value
Use after freeDon't use data after freeing
index out of bounds
Array access past lengthCheck bounds before access
错误原因修复方案
memory leak detected
忘记释放内存添加
defer allocator.free(...)
expected 2 arguments, found 1
ArrayList调用时缺少分配器
append
deinit
添加分配器参数
use of undefined value
释放后使用数据释放后不要使用该数据
index out of bounds
数组访问超出长度访问前检查边界

Pre-commit Checklist

提交前检查清单

  • Every
    alloc
    has corresponding
    defer free
  • Every
    create
    has corresponding
    defer destroy
  • ArrayList uses
    deinit(allocator)
    (0.15+)
  • errdefer
    used for error path cleanup
  • Tests use
    std.testing.allocator
  • No "memory leak detected" in test output
  • No segfaults or crashes
  • Solana programs respect 32KB limit
  • 每一次
    alloc
    都有对应的
    defer free
  • 每一次
    create
    都有对应的
    defer destroy
  • ArrayList使用
    deinit(allocator)
    (0.15+)
  • 错误路径清理使用了
    errdefer
  • 测试使用
    std.testing.allocator
  • 测试输出中没有"memory leak detected"
  • 没有段错误或崩溃
  • Solana程序遵守32KB内存限制

Quick Reference

快速参考

PatternWhen to Use
defer allocator.free(x)
Single allocation cleanup
errdefer allocator.free(x)
Cleanup only on error
defer list.deinit(allocator)
ArrayList cleanup (0.15+)
defer map.deinit()
Managed HashMap cleanup
defer umap.deinit(allocator)
Unmanaged HashMap cleanup
Arena + defer arena.deinit()
Many temporary allocations
std.testing.allocator
Test memory leak detection
模式使用场景
defer allocator.free(x)
单次分配的清理
errdefer allocator.free(x)
仅在错误时清理
defer list.deinit(allocator)
ArrayList清理(0.15+)
defer map.deinit()
托管式HashMap清理
defer umap.deinit(allocator)
非托管式HashMap清理
Arena + defer arena.deinit()
大量临时分配场景
std.testing.allocator
测试内存泄漏检测