Зиг `Ошибка сегментации по адресу` в цикле при выделении и копировании структуры

Я пытаюсь прочитать файл 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 и не имею большого опыта работы с языками, в которых нет сборки мусора. Может ли кто-нибудь помочь мне определить, в чем проблема и как ее исправить? Кроме того, какой подход будет хорошим для устранения подобных проблем в будущем?

Заранее спасибо!

Вам не хватает оператора отсрочки после вызова from_test_path(). Вместо этого ваш код немедленно освобождается options.

sigod 20.08.2024 10:29

Спасибо за предложение! Я добавил оператор defer после вызова from_test_path(), чтобы гарантировать, что параметры не будут освобождены немедленно. Тем не менее, я все еще сталкиваюсь с той же ошибкой сегментации. Есть еще идеи? Или есть что-то еще, что мне следует проверить в коде? Еще раз спасибо за вашу помощь!

somethin 20.08.2024 11:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваша ошибка в том, что вы сохраняете в результате строки из 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.
  • В readJsonFiledefer для 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? Еще раз спасибо!

somethin 21.08.2024 05:45

1. Это опечатка. Должно быть defer std.debug.assert(gpa.deinit() == .ok);. GPA на deinit() проверяет наличие утечек памяти. С помощью assert мы вызываем сбой программы в случае утечек памяти, это не приносит никакой пользы, а просто указывает нам на наличие проблем.

sigod 24.08.2024 12:45

2. Да, но стратегии управления памятью в Zig могут быть очень разнообразными, как и в C. Я бы рекомендовал прочитать раздел Память в документации и исходный код стандартной библиотеки или другого проекта. Писать Зиг немного хлопотно, но потом его вполне читабельно.

sigod 24.08.2024 13:22

Спасибо, что прояснили это, особенно опечатку, теперь это имеет гораздо больше смысла! Я обязательно углублюсь в раздел «Память» в документации и посмотрю те статьи, которые вы упомянули. Похоже, что есть чему поучиться, но я ценю руководство. Еще раз спасибо за помощь!

somethin 26.08.2024 04:58

Другие вопросы по теме