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

Zig动态长度字符串数组内存释放报错,寻求修复方案

Zig动态字符串数组内存释放核心转储问题修复

问题背景

需要编写读取文本文件并返回行字符串数组的函数,先实现了固定长度数组的正常版本,改为动态长度后运行时输出正常,但执行defer的销毁操作时触发核心转储。


1. 固定长度(长度2)的正常代码

运行无内存泄漏:

const std = @import("std");
const stdout = std.io.getStdOut().writer();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};

pub fn main() !void {
    const alloc = gpa.allocator();
    defer _ = gpa.deinit();

    const names = try getNames(alloc);
    defer destroyNames(alloc, names);

    for(names) |name| {
        try stdout.print("Hello {s}\n", .{name});
    }
}

fn destroyNames(alloc:std.mem.Allocator, names:*[2][]const u8) void {
    for(names) |name| {
        // 正确释放[]const u8
        alloc.free(name);
    }
    _ = alloc.destroy(names);
}

fn getNames(alloc:std.mem.Allocator) !*[2][]const u8 {
    const names = [_][]const u8{"Alix", "Bub"};

    const new_names = try alloc.create([2][]const u8);
    const title = "Prof";

    for(names, 0..) |name, i| {
        new_names[i] = try std.fmt.allocPrint(alloc, "{s} {s}", .{title, name} );
    }

    return new_names;
}

2. 动态长度的错误代码

运行输出内容,但defer执行destroyNames时核心转储:

const std = @import("std");
const stdout = std.io.getStdOut().writer();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};

pub fn main() !void {
    const alloc = gpa.allocator();
    defer _ = gpa.deinit();

    const names = try getNames(alloc);
    defer destroyNames(alloc, names);

    for(names.*) |name| {
        try stdout.print("Hello {s}\n", .{name});
    }
}

fn destroyNames(alloc:std.mem.Allocator, names:*[][]const u8) void {
    for(names.*) |name| {
        // 错误:核心转储发生在此处!
        // 无法正确释放传入的[]const u8
        alloc.free(name);
    }
    _ = alloc.destroy(names);
}

fn getNames(alloc:std.mem.Allocator) !*[][]const u8 {
    // 模拟:实际应为读取N行文件
    const names = [_][]const u8{"Alix", "Bub"};

    var new_names:[][]const u8 = undefined;
    new_names = try alloc.create([names.len][]const u8);
    const title = "Prof";

    for(names, 0..) |name, i| {
        new_names[i] = try std.fmt.allocPrint(alloc, "{s} {s}", .{title, name} );
    }

    return &new_names;
}

报错信息

$ zig run length-general.zig 
Hello Prof Alix
Hello Prof Bub
General protection exception (no address available)
/home/tai/.local/var/zig/zig-linux-x86_64-0.14.0-dev.1366+d997ddaa1/lib/compiler_rt/memset.zig:19:14: 0x10f7f10 in memset (compiler_rt)
            d[0] = c;
             ^
/home/tai/.local/var/zig/zig-linux-x86_64-0.14.0-dev.1366+d997ddaa1/lib/std/mem/Allocator.zig:313:26: 0x1040e57 in free__anon_3449 (length-general)
    @memset(non_const_ptr[0..bytes_len], undefined);
                         ^
/home/tai/scratch-zig/length-general.zig:26:19: 0x103bacc in destroyNames (length-general)
        alloc.free(name);
                  ^
/home/tai/scratch-zig/length-general.zig:14:23: 0x103b59c in main (length-general)
    defer destroyNames(alloc, names);
                      ^
/home/tai/.local/var/zig/zig-linux-x86_64-0.14.0-dev.1366+d997ddaa1/lib/std/start.zig:615:37: 0x103ad2f in posixCallMainAndExit (length-general)
            const result = root.main() catch |err| {
                                    ^
/home/tai/.local/var/zig/zig-linux-x86_64-0.14.0-dev.1366+d997ddaa1/lib/std/start.zig:250:5: 0x103a90f in _start (length-general)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
Aborted (core dumped)

尝试使用alloc.free(name.*)时,报错error: index syntax required for slice type '[]const u8',说明接收的确实是[]const u8类型。


问题原因与修复方案

核心问题

动态版本存在两个致命错误:

  1. 返回栈地址:getNames中new_names是栈变量,返回&new_names会导致函数返回后栈空间被回收,后续访问该指针属于未定义行为。
  2. 类型不匹配:alloc.create([names.len][]const u8)返回*[N][]const u8(固定长度数组指针),却被赋值给[][]const u8切片变量,隐式转换丢失了原指针信息,销毁时操作无效内存地址。

修复后的代码(推荐方案:使用切片)

Zig中动态数组的标准处理方式是用切片,代码更简洁且符合语言习惯:

const std = @import("std");
const stdout = std.io.getStdOut().writer();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};

pub fn main() !void {
    const alloc = gpa.allocator();
    defer _ = gpa.deinit();

    const names = try getNames(alloc);
    defer destroyNames(alloc, names);

    for(names) |name| {
        try stdout.print("Hello {s}\n", .{name});
    }
}

fn destroyNames(alloc: std.mem.Allocator, names: [][]const u8) void {
    for(names) |name| {
        alloc.free(name);
    }
    alloc.free(names);
}

fn getNames(alloc: std.mem.Allocator) ![][]const u8 {
    // 模拟读取N行文件
    const raw_names = [_][]const u8{"Alix", "Bub"};
    const title = "Prof";

    // 分配切片内存
    const names = try alloc.alloc([]const u8, raw_names.len);
    errdefer {
        // 分配失败时释放已分配的字符串
        for(names) |name| {
            if (name != undefined) alloc.free(name);
        }
        alloc.free(names);
    }

    for(raw_names, 0..) |name, i| {
        names[i] = try std.fmt.allocPrint(alloc, "{s} {s}", .{title, name});
    }

    return names;
}

关键修复点说明

  1. 用切片替代栈指针:通过alloc.alloc分配切片内存,销毁时先释放每个字符串元素,再释放切片本身。
  2. 避免返回栈地址:栈变量地址在函数返回后失效,绝对不能返回栈指针。
  3. 类型严格匹配:确保分配的内存类型与使用类型一致,杜绝隐式转换导致的内存信息丢失。

内容的提问来源于stack exchange,提问作者tk-noodle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 21:14:59