Я пытаюсь прочитать файл Babel options.json из каталога, но сталкиваюсь с ошибкой сегментации в коде при попытке выделить структуру внутри внутреннего цикла, а затем скопировать эту структуру в другое место во время последней итерации цикла.
const std = @import("std");
const fs = std.fs;
const Allocator = std.mem.Allocator;
const string = []const u8;
const BabelOptions = struct {
source_type: ?string = null,
throws: ?string = null,
plugins: ?[]string = null,
pub fn from_test_path(path: string, allocator: Allocator) !*BabelOptions {
var options = try allocator.create(BabelOptions);
options.* = .{};
var dir = try std.fs.cwd().openDir(path, .{});
defer dir.close();
for (0..3) |_| {
const realpath = try dir.realpathAlloc(allocator, ".");
defer allocator.free(realpath);
const file: ?std.fs.File = dir.openFile("options.json", .{}) catch null;
if (file == null) {
std.debug.print("No options.json found in {s}\n", .{realpath});
continue;
}
const new_json = try readJsonFile(BabelOptions, file.?, allocator);
defer new_json.deinit();
const _new_json = new_json.value;
if (options.source_type == null and _new_json.source_type != null) {
options.source_type = _new_json.source_type;
}
if (options.throws == null and _new_json.throws != null) {
options.throws = _new_json.throws;
}
if (options.plugins == null) {
options.plugins = _new_json.plugins;
} else if (_new_json.plugins) |plugins| {
var list = std.ArrayList([]const u8).init(allocator);
try list.appendSlice(options.plugins.?);
try list.appendSlice(plugins);
const new_plugins = try list.toOwnedSlice();
options.plugins = new_plugins;
}
dir = try dir.openDir("..", .{});
}
return options;
}
};
fn readJsonFile(T: type, file: std.fs.File, allocator: Allocator) !std.json.Parsed(T) {
const file_size = try file.getEndPos();
const source = try allocator.alloc(u8, file_size);
_ = try file.readAll(source);
defer allocator.free(source);
return try std.json.parseFromSlice(T, allocator, source, .{ .ignore_unknown_fields = true, .allocate = .alloc_always });
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const path = "src/test/fixtures/babel/t0/t1/t2";
const options =
try BabelOptions.from_test_path(path, allocator);
defer allocator.destroy(options);
std.log.debug("source_type: {any}", .{options.source_type});
}
Сообщение об ошибке, которое я получаю:
PS D:\project> zig build-exe .\src\test\main.zig
PS D:\project> .\main.exe
Segmentation fault at address 0x1e3c0cb0010
Похоже, что ошибка сегментации может быть связана с неправильным освобождением памяти или неправильным доступом к ней. Я не совсем уверен, в чем заключается проблема, но, скорее всего, она связана с управлением памятью внутри цикла.
Я новичок в Zig и не имею большого опыта работы с языками, в которых нет сборки мусора. Может ли кто-нибудь помочь мне определить, в чем проблема и как ее исправить? Кроме того, какой подход будет хорошим для устранения подобных проблем в будущем?
Заранее спасибо!
Спасибо за предложение! Я добавил оператор defer после вызова from_test_path(), чтобы гарантировать, что параметры не будут освобождены немедленно. Тем не менее, я все еще сталкиваюсь с той же ошибкой сегментации. Есть еще идеи? Или есть что-то еще, что мне следует проверить в коде? Еще раз спасибо за вашу помощь!
Ваша ошибка в том, что вы сохраняете в результате строки из JSON, но эти строки освобождаются вместе с JSON.
Например:
options.source_type = _new_json.source_type;
Это должно быть:
options.source_type = try allocator.dupe(u8, _new_json.source_type);
Вам нужно будет освободить от опций не только опции, но и отдельные строки. Для этого вы можете добавить функцию в свой BabelOptions
.
И я бы рекомендовал:
from_test_path
вместо возврата указателя на структуру. Возврат указателя ограничивает возможности действий со структурой.errdefer
после каждого распределения, которое иначе не имеет defer
.ArrayList
для плагинов вместо выделения нескольких в if
.readJsonFile
defer
для source
должен появляться сразу после выделения, а не после чтения. В противном случае, если чтение не удастся, source
не будет освобожден.defer std.debug.assert(gpa.deinit() == .ok);
Это будет выглядеть примерно так:
const std = @import("std");
const fs = std.fs;
const Allocator = std.mem.Allocator;
const string = []const u8;
const BabelOptions = struct {
source_type: ?string = null,
throws: ?string = null,
plugins: ?[]string = null,
pub fn free(self: BabelOptions, allocator: Allocator) void {
if (self.source_type) |source_type| {
allocator.free(source_type);
}
if (self.throws) |throws| {
allocator.free(throws);
}
if (self.plugins) |plugins| {
for (plugins) |plugin| {
allocator.free(plugin);
}
allocator.free(plugins);
}
}
pub fn from_test_path(path: string, allocator: Allocator) !BabelOptions {
var options: BabelOptions = .{};
errdefer options.free(allocator);
var plugin_list = std.ArrayList([]const u8).init(allocator);
errdefer {
for (plugin_list.items) |plugin| {
allocator.free(plugin);
}
plugin_list.deinit();
}
var dir = try std.fs.cwd().openDir(path, .{});
defer dir.close();
for (0..3) |_| {
const realpath = try dir.realpathAlloc(allocator, ".");
defer allocator.free(realpath);
const file: ?std.fs.File = dir.openFile("options.json", .{}) catch null;
if (file == null) {
std.debug.print("No options.json found in {s}\n", .{realpath});
continue;
}
const new_json = try readJsonFile(BabelOptions, file.?, allocator);
defer new_json.deinit();
const _new_json = new_json.value;
if (options.source_type == null and _new_json.source_type != null) {
options.source_type = try allocator.dupe(u8, _new_json.source_type.?);
}
if (options.throws == null and _new_json.throws != null) {
options.throws = try allocator.dupe(u8, _new_json.throws.?);
}
if (_new_json.plugins) |plugins| {
for (plugins) |plugin| {
try plugin_list.append(try allocator.dupe(u8, plugin));
}
}
dir = try dir.openDir("..", .{});
}
options.plugins = try plugin_list.toOwnedSlice();
return options;
}
};
fn readJsonFile(T: type, file: std.fs.File, allocator: Allocator) !std.json.Parsed(T) {
const file_size = try file.getEndPos();
const source = try allocator.alloc(u8, file_size);
defer allocator.free(source);
_ = try file.readAll(source);
return try std.json.parseFromSlice(T, allocator, source, .{ .ignore_unknown_fields = true, .allocate = .alloc_always });
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(gpa.deinit() == .ok);
const allocator = gpa.allocator();
const path = "src/test/fixtures/babel/t0/t1/t2";
const options = try BabelOptions.from_test_path(path, allocator);
defer options.free(allocator);
std.log.debug("source_type: {any}", .{options.source_type});
}
Спасибо большое за подробное объяснение, очень помогло! Код работает сейчас. Однако у меня есть еще два вопроса: 1. Меня все еще немного смущает часть defer std.debug.assert(gpa.deinit() != .ok);
, не могли бы вы объяснить это подробнее? 2. Являются ли упомянутые вами методы управления памятью стандартными для Zig? Еще раз спасибо!
1. Это опечатка. Должно быть defer std.debug.assert(gpa.deinit() == .ok);
. GPA на deinit()
проверяет наличие утечек памяти. С помощью assert
мы вызываем сбой программы в случае утечек памяти, это не приносит никакой пользы, а просто указывает нам на наличие проблем.
2. Да, но стратегии управления памятью в Zig могут быть очень разнообразными, как и в C. Я бы рекомендовал прочитать раздел Память в документации и исходный код стандартной библиотеки или другого проекта. Писать Зиг немного хлопотно, но потом его вполне читабельно.
В Интернете также есть несколько статей об управлении памятью: например. Управление памятью с помощью Zig , Куча памяти и распределители , Память и распределители и т. д. Также могут быть полезны статьи, не относящиеся к Zig, например Стратегии распределения памяти.
Спасибо, что прояснили это, особенно опечатку, теперь это имеет гораздо больше смысла! Я обязательно углублюсь в раздел «Память» в документации и посмотрю те статьи, которые вы упомянули. Похоже, что есть чему поучиться, но я ценю руководство. Еще раз спасибо за помощь!
Вам не хватает оператора отсрочки после вызова
from_test_path()
. Вместо этого ваш код немедленно освобождаетсяoptions
.