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类型。
问题原因与修复方案
核心问题
动态版本存在两个致命错误:
- 返回栈地址:
getNames中new_names是栈变量,返回&new_names会导致函数返回后栈空间被回收,后续访问该指针属于未定义行为。 - 类型不匹配:
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; }
关键修复点说明
- 用切片替代栈指针:通过
alloc.alloc分配切片内存,销毁时先释放每个字符串元素,再释放切片本身。 - 避免返回栈地址:栈变量地址在函数返回后失效,绝对不能返回栈指针。
- 类型严格匹配:确保分配的内存类型与使用类型一致,杜绝隐式转换导致的内存信息丢失。
内容的提问来源于stack exchange,提问作者tk-noodle
相关产品推荐
相关产品推荐

