You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Zig中ArrayList动态分配条目内存泄漏问题修复求助

修复Zig UTF-8字符串分割函数的内存泄漏与崩溃问题

问题根源

你的代码存在三个核心问题:

  • 初始追加的空字符串是静态常量"",后续执行allocator.free(last)时尝试释放静态内存,触发未定义行为(崩溃或内存错误)。
  • std.mem.concat的错误处理逻辑错误,直接返回字符串字面量不符合函数的返回类型要求。
  • 测试代码仅调用actual.deinit()不会释放ArrayList中每个元素的内存,导致内存泄漏。

修复后的函数代码

const std = @import("std");

fn split_utf8(allocator: std.mem.Allocator, in: []const u8, delimiter: []const u8) !std.ArrayList([]const u8) {
    var list = std.ArrayList([]const u8).init(allocator);
    // 分配堆内存的空字符串,确保后续可安全释放
    try list.append(try allocator.dupe(u8, ""));
    
    var utf8 = (try std.unicode.Utf8View.init(in)).iterator();
    while (utf8.nextCodepointSlice()) |codepoint| {
        if (std.mem.eql(u8, codepoint, delimiter)) {
            // 同样分配堆内存的空字符串作为新条目初始值
            try list.append(try allocator.dupe(u8, ""));
            continue;
        }

        const last = list.pop();
        defer allocator.free(last);

        // 正确传播concat的错误,而非返回无效的字符串字面量
        const new = try std.mem.concat(allocator, u8, &[_][]const u8{last, codepoint});
        try list.append(new);
    }

    return list;
}

修复后的测试代码

test "Split test" {
    const testing = std.testing;

    var expected = std.ArrayList([]const u8).init(testing.allocator);
    defer expected.deinit();
    try expected.append("F");
    try expected.append("1");
    try expected.append("100");
    try expected.append("📷click");
    
    const actual = try split_utf8(testing.allocator, "F 1 100 📷click", " ");
    defer {
        // 先释放每个元素的堆内存,再释放ArrayList自身结构
        for (actual.items) |item| {
            testing.allocator.free(item);
        }
        actual.deinit();
    }
    
    try testing.expectEqual(expected.items.len, actual.items.len);
    
    // 验证每个分割结果的内容一致性
    for (expected.items, actual.items) |exp, act| {
        try testing.expectEqualStrings(exp, act);
    }
}

关键修复点说明

  1. 统一堆分配元素:所有列表条目都使用allocator.dupe分配堆内存,避免静态内存与堆内存混合导致的释放错误。
  2. 正确错误传播:移除错误分支的无效字符串返回,使用try传递std.mem.concat的原生错误,符合函数的返回类型约定。
  3. 完整内存释放:测试代码中先遍历释放每个元素的内存,再调用deinit释放ArrayList自身的结构内存,彻底解决泄漏问题。

优化方案(减少内存分配次数)

上述修复版本每次拼接都重新分配内存,效率较低。可以改用std.ArrayList(u8)动态构建每个条目,减少分配次数:

fn split_utf8(allocator: std.mem.Allocator, in: []const u8, delimiter: []const u8) !std.ArrayList([]const u8) {
    var list = std.ArrayList([]const u8).init(allocator);
    var current = std.ArrayList(u8).init(allocator);
    defer current.deinit();

    var utf8 = (try std.unicode.Utf8View.init(in)).iterator();
    while (utf8.nextCodepointSlice()) |codepoint| {
        if (std.mem.eql(u8, codepoint, delimiter)) {
            // 将当前构建的字符串转为堆分配切片,加入结果列表
            try list.append(try current.toOwnedSlice());
            current.clearRetainingCapacity();
            continue;
        }

        try current.appendSlice(codepoint);
    }

    // 加入最后一个未完成的条目
    try list.append(try current.toOwnedSlice());

    return list;
}

这个版本用动态数组逐字符构建条目,避免了多次拼接分配,内存效率更高,释放逻辑与之前一致。

内容的提问来源于stack exchange,提问作者lukstru

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 07:13:11