Skip to content

Zig 内存管理

内存管理是系统编程的核心概念,Zig 提供了强大而灵活的内存管理机制,让开发者能够精确控制内存的分配和释放。

内存管理基础

栈内存 vs 堆内存

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

pub fn main() void {
    // 栈内存:自动管理,函数结束时自动释放
    var stack_array: [100]i32 = undefined;
    const stack_value = 42;
    
    std.debug.print("栈变量地址: {*}\n", .{&stack_value});
    std.debug.print("栈数组地址: {*}\n", .{&stack_array});
    
    // 栈内存的生命周期由作用域决定
    {
        var local_var = 123;
        std.debug.print("局部变量地址: {*}\n", .{&local_var});
    } // local_var 在这里被销毁
}

分配器概念

Zig 使用分配器(Allocator)来管理堆内存:

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

pub fn main() !void {
    // 创建通用分配器
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // 分配内存
    const memory = try allocator.alloc(u8, 100);
    defer allocator.free(memory); // 重要:释放内存
    
    std.debug.print("分配了 {} 字节内存\n", .{memory.len});
    std.debug.print("内存地址: {*}\n", .{memory.ptr});
    
    // 使用内存
    for (memory, 0..) |*byte, i| {
        byte.* = @intCast(i % 256);
    }
    
    std.debug.print("前10个字节: ");
    for (memory[0..10]) |byte| {
        std.debug.print("{} ", .{byte});
    }
    std.debug.print("\n");
}

分配器类型

通用分配器 (GeneralPurposeAllocator)

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

pub fn main() !void {
    // 通用分配器:适合大多数用途,有内存泄漏检测
    var gpa = std.heap.GeneralPurposeAllocator(.{
        .safety = true,  // 启用安全检查
        .thread_safe = true,  // 线程安全
    }){};
    defer {
        const leaked = gpa.deinit();
        if (leaked == .leak) {
            std.debug.print("检测到内存泄漏!\n", .{});
        }
    }
    
    const allocator = gpa.allocator();
    
    // 分配不同大小的内存
    const small_mem = try allocator.alloc(u8, 10);
    const large_mem = try allocator.alloc(i32, 1000);
    
    defer allocator.free(small_mem);
    defer allocator.free(large_mem);
    
    std.debug.print("小内存: {} 字节\n", .{small_mem.len});
    std.debug.print("大内存: {} 个整数\n", .{large_mem.len});
}

页分配器 (PageAllocator)

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

pub fn main() !void {
    // 页分配器:直接从操作系统分配页面
    const allocator = std.heap.page_allocator;
    
    // 分配大块内存
    const big_memory = try allocator.alloc(u8, 4096 * 10); // 10 页
    defer allocator.free(big_memory);
    
    std.debug.print("分配了 {} 字节 ({} 页)\n", .{ big_memory.len, big_memory.len / 4096 });
    
    // 页分配器适合大内存分配
    const huge_array = try allocator.alloc(f64, 100000);
    defer allocator.free(huge_array);
    
    std.debug.print("分配了 {} 个浮点数\n", .{huge_array.len});
}

固定缓冲区分配器 (FixedBufferAllocator)

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

pub fn main() !void {
    // 预分配缓冲区
    var buffer: [1024]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();
    
    // 从固定缓冲区分配内存
    const mem1 = try allocator.alloc(u8, 100);
    const mem2 = try allocator.alloc(i32, 50);
    
    std.debug.print("从固定缓冲区分配:\n");
    std.debug.print("  内存1: {} 字节\n", .{mem1.len});
    std.debug.print("  内存2: {} 个整数\n", .{mem2.len});
    std.debug.print("  剩余空间: {} 字节\n", .{fba.end_index});
    
    // 固定缓冲区分配器不需要显式释放
    // 当 fba 超出作用域时,所有内存都会被释放
}

竞技场分配器 (ArenaAllocator)

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    
    // 创建竞技场分配器
    var arena = std.heap.ArenaAllocator.init(gpa.allocator());
    defer arena.deinit(); // 一次性释放所有内存
    
    const allocator = arena.allocator();
    
    // 分配多个内存块
    var allocations = std.ArrayList([]u8).init(gpa.allocator());
    defer allocations.deinit();
    
    for (0..10) |i| {
        const size = (i + 1) * 10;
        const memory = try allocator.alloc(u8, size);
        try allocations.append(memory);
        
        std.debug.print("分配 {}: {} 字节\n", .{ i, size });
    }
    
    std.debug.print("总共分配了 {} 个内存块\n", .{allocations.items.len});
    
    // arena.deinit() 会释放所有分配的内存
    // 不需要单独释放每个内存块
}

内存分配和释放

基本分配操作

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // alloc: 分配未初始化的内存
    const uninit_memory = try allocator.alloc(i32, 5);
    defer allocator.free(uninit_memory);
    
    // 初始化内存
    for (uninit_memory, 0..) |*item, i| {
        item.* = @intCast(i * 10);
    }
    
    std.debug.print("未初始化内存: ");
    for (uninit_memory) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // allocSentinel: 分配带哨兵值的内存
    const sentinel_memory = try allocator.allocSentinel(u8, 10, 0);
    defer allocator.free(sentinel_memory);
    
    // 复制字符串到哨兵内存
    @memcpy(sentinel_memory[0..5], "Hello");
    std.debug.print("哨兵内存: {s}\n", .{sentinel_memory});
    
    // dupe: 复制现有数据
    const original = [_]i32{ 1, 2, 3, 4, 5 };
    const duplicated = try allocator.dupe(i32, &original);
    defer allocator.free(duplicated);
    
    std.debug.print("复制的数据: ");
    for (duplicated) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
}

动态调整大小

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // 初始分配
    var memory = try allocator.alloc(i32, 5);
    defer allocator.free(memory);
    
    // 初始化数据
    for (memory, 0..) |*item, i| {
        item.* = @intCast(i + 1);
    }
    
    std.debug.print("初始内存 ({} 个元素): ", .{memory.len});
    for (memory) |item| {
        std.debug.print("{} ", .{item});
    }
    std.debug.print("\n");
    
    // 尝试扩展内存
    if (allocator.resize(memory, 10)) {
        memory = memory.ptr[0..10];
        
        // 初始化新的元素
        for (memory[5..]) |*item| {
            item.* = 0;
        }
        
        std.debug.print("扩展后内存 ({} 个元素): ", .{memory.len});
        for (memory) |item| {
            std.debug.print("{} ", .{item});
        }
        std.debug.print("\n");
    } else {
        std.debug.print("无法就地扩展内存\n", .{});
        
        // 重新分配更大的内存
        const new_memory = try allocator.realloc(memory, 10);
        memory = new_memory;
        
        std.debug.print("重新分配后内存 ({} 个元素): ", .{memory.len});
        for (memory) |item| {
            std.debug.print("{} ", .{item});
        }
        std.debug.print("\n");
    }
}

内存对齐

对齐要求

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // 分配对齐的内存
    const aligned_memory = try allocator.alignedAlloc(u8, 16, 64);
    defer allocator.free(aligned_memory);
    
    std.debug.print("内存地址: {*}\n", .{aligned_memory.ptr});
    std.debug.print("地址对齐检查 (16字节): {}\n", .{@intFromPtr(aligned_memory.ptr) % 16 == 0});
    
    // 不同类型的对齐要求
    std.debug.print("\n类型对齐要求:\n");
    std.debug.print("u8: {} 字节\n", .{@alignOf(u8)});
    std.debug.print("u16: {} 字节\n", .{@alignOf(u16)});
    std.debug.print("u32: {} 字节\n", .{@alignOf(u32)});
    std.debug.print("u64: {} 字节\n", .{@alignOf(u64)});
    std.debug.print("f64: {} 字节\n", .{@alignOf(f64)});
    
    // 结构体对齐
    const Point = struct {
        x: f64,
        y: f64,
    };
    
    std.debug.print("Point 结构体: {} 字节\n", .{@alignOf(Point)});
}

内存安全

内存泄漏检测

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{
        .safety = true,
    }){};
    
    const allocator = gpa.allocator();
    
    // 正常的内存使用
    const good_memory = try allocator.alloc(u8, 100);
    allocator.free(good_memory);
    
    // 故意创建内存泄漏(仅用于演示)
    const leaked_memory = try allocator.alloc(u8, 50);
    _ = leaked_memory; // 故意不释放
    
    // 检查内存泄漏
    const leaked = gpa.deinit();
    switch (leaked) {
        .ok => std.debug.print("没有内存泄漏\n", .{}),
        .leak => std.debug.print("检测到内存泄漏!\n", .{}),
    }
}

双重释放检测

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{
        .safety = true,
    }){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    const memory = try allocator.alloc(u8, 100);
    
    // 正常释放
    allocator.free(memory);
    
    // 尝试双重释放(在安全模式下会被检测到)
    // allocator.free(memory); // 这会导致恐慌
    
    std.debug.print("内存安全检查完成\n", .{});
}

自定义分配器

简单的计数分配器

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

const CountingAllocator = struct {
    child_allocator: std.mem.Allocator,
    allocation_count: usize,
    deallocation_count: usize,
    bytes_allocated: usize,
    
    const Self = @This();
    
    pub fn init(child_allocator: std.mem.Allocator) Self {
        return Self{
            .child_allocator = child_allocator,
            .allocation_count = 0,
            .deallocation_count = 0,
            .bytes_allocated = 0,
        };
    }
    
    pub fn allocator(self: *Self) std.mem.Allocator {
        return std.mem.Allocator{
            .ptr = self,
            .vtable = &.{
                .alloc = alloc,
                .resize = resize,
                .free = free,
            },
        };
    }
    
    fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
        const self: *Self = @ptrCast(@alignCast(ctx));
        const result = self.child_allocator.rawAlloc(len, ptr_align, ret_addr);
        if (result != null) {
            self.allocation_count += 1;
            self.bytes_allocated += len;
        }
        return result;
    }
    
    fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
        const self: *Self = @ptrCast(@alignCast(ctx));
        return self.child_allocator.rawResize(buf, buf_align, new_len, ret_addr);
    }
    
    fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
        const self: *Self = @ptrCast(@alignCast(ctx));
        self.child_allocator.rawFree(buf, buf_align, ret_addr);
        self.deallocation_count += 1;
    }
    
    pub fn printStats(self: *const Self) void {
        std.debug.print("分配统计:\n");
        std.debug.print("  分配次数: {}\n", .{self.allocation_count});
        std.debug.print("  释放次数: {}\n", .{self.deallocation_count});
        std.debug.print("  分配字节数: {}\n", .{self.bytes_allocated});
        std.debug.print("  未释放次数: {}\n", .{self.allocation_count - self.deallocation_count});
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    
    var counting_allocator = CountingAllocator.init(gpa.allocator());
    const allocator = counting_allocator.allocator();
    
    // 使用计数分配器
    const mem1 = try allocator.alloc(u8, 100);
    const mem2 = try allocator.alloc(i32, 50);
    const mem3 = try allocator.alloc(f64, 25);
    
    counting_allocator.printStats();
    
    // 释放部分内存
    allocator.free(mem1);
    allocator.free(mem2);
    
    counting_allocator.printStats();
    
    // 释放剩余内存
    allocator.free(mem3);
    
    counting_allocator.printStats();
}

内存池

对象池实现

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

fn ObjectPool(comptime T: type) type {
    return struct {
        const Self = @This();
        const Node = struct {
            data: T,
            next: ?*Node,
        };
        
        allocator: std.mem.Allocator,
        free_list: ?*Node,
        allocated_nodes: std.ArrayList(*Node),
        
        pub fn init(allocator: std.mem.Allocator) Self {
            return Self{
                .allocator = allocator,
                .free_list = null,
                .allocated_nodes = std.ArrayList(*Node).init(allocator),
            };
        }
        
        pub fn deinit(self: *Self) void {
            for (self.allocated_nodes.items) |node| {
                self.allocator.destroy(node);
            }
            self.allocated_nodes.deinit();
        }
        
        pub fn acquire(self: *Self) !*T {
            if (self.free_list) |node| {
                self.free_list = node.next;
                return &node.data;
            }
            
            // 创建新节点
            const node = try self.allocator.create(Node);
            try self.allocated_nodes.append(node);
            return &node.data;
        }
        
        pub fn release(self: *Self, obj: *T) void {
            const node = @fieldParentPtr(Node, "data", obj);
            node.next = self.free_list;
            self.free_list = node;
        }
        
        pub fn getStats(self: *const Self) struct { total: usize, free: usize } {
            var free_count: usize = 0;
            var current = self.free_list;
            while (current) |node| {
                free_count += 1;
                current = node.next;
            }
            
            return .{
                .total = self.allocated_nodes.items.len,
                .free = free_count,
            };
        }
    };
}

const Point = struct {
    x: f32,
    y: f32,
    
    pub fn init(x: f32, y: f32) Point {
        return Point{ .x = x, .y = y };
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    var pool = ObjectPool(Point).init(allocator);
    defer pool.deinit();
    
    // 获取对象
    var points = std.ArrayList(*Point).init(allocator);
    defer points.deinit();
    
    for (0..5) |i| {
        const point = try pool.acquire();
        point.* = Point.init(@floatFromInt(i), @floatFromInt(i * 2));
        try points.append(point);
    }
    
    var stats = pool.getStats();
    std.debug.print("池统计 - 总数: {}, 空闲: {}, 使用中: {}\n", 
                    .{ stats.total, stats.free, stats.total - stats.free });
    
    // 释放部分对象
    for (points.items[0..3]) |point| {
        pool.release(point);
    }
    
    stats = pool.getStats();
    std.debug.print("释放后 - 总数: {}, 空闲: {}, 使用中: {}\n", 
                    .{ stats.total, stats.free, stats.total - stats.free });
    
    // 重新获取对象(应该重用已释放的对象)
    const reused_point = try pool.acquire();
    reused_point.* = Point.init(100, 200);
    
    stats = pool.getStats();
    std.debug.print("重用后 - 总数: {}, 空闲: {}, 使用中: {}\n", 
                    .{ stats.total, stats.free, stats.total - stats.free });
}

内存管理最佳实践

1. RAII 模式

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

const ManagedBuffer = struct {
    data: []u8,
    allocator: std.mem.Allocator,
    
    const Self = @This();
    
    pub fn init(allocator: std.mem.Allocator, size: usize) !Self {
        const data = try allocator.alloc(u8, size);
        return Self{
            .data = data,
            .allocator = allocator,
        };
    }
    
    pub fn deinit(self: *Self) void {
        self.allocator.free(self.data);
    }
    
    pub fn fill(self: *Self, value: u8) void {
        @memset(self.data, value);
    }
    
    pub fn getData(self: *const Self) []const u8 {
        return self.data;
    }
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    // 使用 RAII 模式管理内存
    var buffer = try ManagedBuffer.init(allocator, 100);
    defer buffer.deinit(); // 自动清理
    
    buffer.fill(42);
    
    std.debug.print("缓冲区大小: {}\n", .{buffer.getData().len});
    std.debug.print("前5个字节: ");
    for (buffer.getData()[0..5]) |byte| {
        std.debug.print("{} ", .{byte});
    }
    std.debug.print("\n");
}

2. 使用 defer 确保清理

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

fn processData(allocator: std.mem.Allocator) !void {
    // 分配多个资源
    const buffer1 = try allocator.alloc(u8, 1000);
    defer allocator.free(buffer1);
    
    const buffer2 = try allocator.alloc(i32, 500);
    defer allocator.free(buffer2);
    
    const buffer3 = try allocator.alloc(f64, 200);
    defer allocator.free(buffer3);
    
    // 处理数据...
    std.debug.print("处理 {} + {} + {} 字节数据\n", 
                    .{ buffer1.len, buffer2.len * @sizeOf(i32), buffer3.len * @sizeOf(f64) });
    
    // 所有 defer 语句会在函数结束时自动执行
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    try processData(allocator);
    
    std.debug.print("数据处理完成,内存已清理\n", .{});
}

3. 选择合适的分配器

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

pub fn main() !void {
    // 对于短生命周期的大量小分配,使用 Arena
    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();
    
    const arena_allocator = arena.allocator();
    
    // 大量小分配
    var small_allocations = std.ArrayList([]u8).init(std.heap.page_allocator);
    defer small_allocations.deinit();
    
    for (0..1000) |i| {
        const size = (i % 100) + 1;
        const memory = try arena_allocator.alloc(u8, size);
        try small_allocations.append(memory);
    }
    
    std.debug.print("使用 Arena 分配了 {} 个小内存块\n", .{small_allocations.items.len});
    
    // 对于固定大小的临时工作,使用 FixedBufferAllocator
    var buffer: [4096]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const fixed_allocator = fba.allocator();
    
    const temp_memory = try fixed_allocator.alloc(u8, 1000);
    std.debug.print("从固定缓冲区分配了 {} 字节\n", .{temp_memory.len});
    
    // 对于长生命周期的精确控制,使用 GeneralPurposeAllocator
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const gp_allocator = gpa.allocator();
    
    const precise_memory = try gp_allocator.alloc(f64, 10000);
    defer gp_allocator.free(precise_memory);
    
    std.debug.print("精确分配了 {} 个浮点数\n", .{precise_memory.len});
}

总结

本章详细介绍了 Zig 的内存管理系统:

  • ✅ 栈内存和堆内存的区别
  • ✅ 各种分配器类型及其适用场景
  • ✅ 内存分配、释放和调整大小
  • ✅ 内存对齐和安全检查
  • ✅ 自定义分配器的实现
  • ✅ 内存池和对象池模式
  • ✅ 内存管理的最佳实践

Zig 的内存管理系统提供了极大的灵活性和控制力,让开发者能够根据具体需求选择最合适的内存管理策略。在下一章中,我们将学习 Zig 的编译期特性。

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