zig-memory
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseZig Memory Management Guide
Zig内存管理指南
Core Principle: Every allocation must have a corresponding deallocation. Usefor normal cleanup,deferfor error path cleanup.errdefer
This skill ensures safe memory management in Zig, preventing memory leaks and use-after-free bugs.
Official Documentation:
- Memory Allocators: https://ziglang.org/documentation/0.15.2/#Memory
- std.mem: https://ziglang.org/documentation/0.15.2/std/#std.mem
Related Skills:
- : API changes including ArrayList allocator parameter
zig-0.15 - : Solana-specific memory constraints (32KB heap)
solana-sdk-zig
核心原则:每一次内存分配都必须有对应的释放操作。 使用处理常规清理,使用defer处理错误路径的清理。errdefer
本技能可确保Zig中的内存管理安全,避免内存泄漏和释放后使用的bug。
官方文档:
- 内存分配器:https://ziglang.org/documentation/0.15.2/#Memory
- std.mem:https://ziglang.org/documentation/0.15.2/std/#std.mem
相关技能:
- :包含ArrayList分配器参数在内的API变更
zig-0.15 - :Solana特定的内存限制(32KB堆内存)
solana-sdk-zig
References
参考资料
Detailed allocator patterns and examples:
| Document | Path | Content |
|---|---|---|
| Allocator Patterns | | GPA, Arena, FixedBuffer, Testing allocators, BPF allocator |
详细的分配器模式及示例:
| 文档 | 路径 | 内容 |
|---|---|---|
| 分配器模式 | | 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+)
| Method | Allocator? | Notes |
|---|---|---|
| Yes | Preferred initialization |
| Yes | Changed in 0.15! |
| Yes | Changed in 0.15! |
| Yes | Changed in 0.15! |
| Yes | Returns pointer to new slot |
| Yes | Pre-allocate capacity |
| Yes | Caller must free result |
| No | Assumes capacity exists |
| No | Read-only access |
| 方法 | 是否需要分配器 | 说明 |
|---|---|---|
| 是 | 推荐的初始化方式 |
| 是 | 0.15版本变更! |
| 是 | 0.15版本变更! |
| 是 | 0.15版本变更! |
| 是 | 返回新插槽的指针 |
| 是 | 预分配容量 |
| 是 | 调用者必须释放返回结果 |
| 否 | 假设容量已存在 |
| 否 | 只读访问 |
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 neededzig
// 托管式 - 内部存储分配器
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 requiredzig
// 非托管式 - 每次操作都需要分配器
var umap = std.StringHashMapUnmanaged(V){};
defer umap.deinit(allocator); // 必须传入分配器
try umap.put(allocator, key, value); // 必须传入分配器Which to Use?
如何选择?
| Type | When to Use |
|---|---|
Managed ( | General use, simpler API |
Unmanaged ( | When allocator changes, performance-critical |
| 类型 | 使用场景 |
|---|---|
托管式( | 通用场景,API更简洁 |
非托管式( | 分配器会变更的场景、性能关键场景 |
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 Case | Why Arena |
|---|---|
| Temporary computations | Free all at once |
| Request handling | Allocate per request, free at end |
| Parsing | Allocate AST nodes, free when done |
| Building strings | Accumulate, then transfer ownership |
| 使用场景 | 选择Arena的原因 |
|---|---|
| 临时计算 | 一次性释放所有内存 |
| 请求处理 | 为每个请求分配内存,请求结束时释放 |
| 解析操作 | 分配AST节点,完成后释放 |
| 字符串构建 | 累积内容,然后转移所有权 |
Testing Allocator (Leak Detection)
测试用分配器(泄漏检测)
std.testing.allocatorzig
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.allocatorzig
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 pointzig
// ❌ 危险 - 释放后使用
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 freeszig
// 拥有 - 调用者获取所有权并必须释放
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内存限制
| Constraint | Value |
|---|---|
| Total heap | 32KB |
| Free support | ❌ None |
| Stack size | 64KB (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 for zero-copy parsing
extern struct
- 尽可能预先计算大小
- 小型/固定大小的分配使用栈内存
- 复用缓冲区而非重新分配
- 使用进行零拷贝解析
extern struct
Common Error Messages
常见错误信息
| Error | Cause | Fix |
|---|---|---|
| Forgot to free | Add |
| ArrayList missing allocator | Add allocator to |
| Use after free | Don't use data after freeing |
| Array access past length | Check bounds before access |
| 错误 | 原因 | 修复方案 |
|---|---|---|
| 忘记释放内存 | 添加 |
| ArrayList调用时缺少分配器 | 为 |
| 释放后使用数据 | 释放后不要使用该数据 |
| 数组访问超出长度 | 访问前检查边界 |
Pre-commit Checklist
提交前检查清单
- Every has corresponding
allocdefer free - Every has corresponding
createdefer destroy - ArrayList uses (0.15+)
deinit(allocator) - used for error path cleanup
errdefer - Tests use
std.testing.allocator - No "memory leak detected" in test output
- No segfaults or crashes
- Solana programs respect 32KB limit
- 每一次都有对应的
allocdefer free - 每一次都有对应的
createdefer destroy - ArrayList使用(0.15+)
deinit(allocator) - 错误路径清理使用了
errdefer - 测试使用
std.testing.allocator - 测试输出中没有"memory leak detected"
- 没有段错误或崩溃
- Solana程序遵守32KB内存限制
Quick Reference
快速参考
| Pattern | When to Use |
|---|---|
| Single allocation cleanup |
| Cleanup only on error |
| ArrayList cleanup (0.15+) |
| Managed HashMap cleanup |
| Unmanaged HashMap cleanup |
| Many temporary allocations |
| Test memory leak detection |
| 模式 | 使用场景 |
|---|---|
| 单次分配的清理 |
| 仅在错误时清理 |
| ArrayList清理(0.15+) |
| 托管式HashMap清理 |
| 非托管式HashMap清理 |
| 大量临时分配场景 |
| 测试内存泄漏检测 |