如何在 Zig 的 comptime 中指定分配器?

How Can I Specify an Allocator at Comptime in Zig?

提问人:William Ryman 提问时间:10/24/2023 更新时间:11/1/2023 访问量:192

问:

在 Zig 中,我想指定一个分配器,以消除将分配器传递给每个需要它的函数的额外开销。在下面的 MWE 中,我有一个类型生成器,它接受并返回一个类型,其中该分配器作为“静态”成员。实例化类型,调用 ,然后使用先前指定的分配器初始化对象。comptimecomptimeArenaAllocatorMyTypeMyType

const std = @import("std");

fn MyTypeGen(comptime arena: *std.heap.ArenaAllocator) type {
    return struct {
        var allocator = arena.allocator();

        data: []const u8,

        pub fn init(data: []const u8) !@This() {
            const temp = try allocator.alloc(u8, data.len);
            @memcpy(temp, data);

            return .{ .data = temp };
        }
    };
}

pub fn main() !void {
    comptime var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
    defer arena.deinit();

    const MyType = MyTypeGen(&arena);

    const val = try MyType.init(&[_]u8{ 1, 2, 3 });

    std.debug.print("{}\n", .{val});
}

问题是上面的代码失败并出现隔离错误

Segmentation fault at address 0x7ff63e46b628
C:\Program Files\zig\lib\std\heap\arena_allocator.zig:171:39: 0x7ff63e3f2788 in createNode (Calculator.exe.obj)
        self.state.buffer_list.prepend(buf_node);
                                      ^
C:\Program Files\zig\lib\std\heap\arena_allocator.zig:184:29: 0x7ff63e3f216a in alloc (Calculator.exe.obj)
            (self.createNode(0, n + ptr_align) orelse return null);
                            ^
C:\Program Files\zig\lib\std\mem\Allocator.zig:225:53: 0x7ff63e4242dd in allocBytesWithAlignment__anon_6797 (Calculator.exe.obj)
    const byte_ptr = self.rawAlloc(byte_count, log2a(alignment), return_address) orelse return Error.OutOfMemory;
                                                    ^
C:\Program Files\zig\lib\std\mem\Allocator.zig:211:40: 0x7ff63e3f2e37 in allocWithSizeAndAlignment__anon_3321 (Calculator.exe.obj)
    return self.allocBytesWithAlignment(alignment, byte_count, return_address);
                                       ^
C:\Program Files\zig\lib\std\mem\Allocator.zig:129:41: 0x7ff63e3f11f3 in alloc__anon_3262 (Calculator.exe.obj)
    return self.allocAdvancedWithRetAddr(T, null, n, @returnAddress());
                                        ^
C:\...\src\main.zig:10:45: 0x7ff63e3f1044 in init (Calculator.exe.obj)
            const temp = try allocator.alloc(u8, data.len);
                                            ^
C:\...\src\main.zig:24:32: 0x7ff63e3f1486 in main (Program.exe.obj)
    const val = try MyType.init(&[_]u8{ 1, 2, 3 });
                               ^
C:\Program Files\zig\lib\std\start.zig:348:65: 0x7ff63e3f175c in WinStartup (Program.exe.obj)
    std.os.windows.kernel32.ExitProcess(initEventLoopAndCallMain());
                                                                ^
???:?:?: 0x7ffee8867343 in ??? (KERNEL32.DLL)
???:?:?: 0x7ffeea8426b0 in ??? (ntdll.dll)
run Program: error: the following command exited with error code 3:
C:\...\zig-out\bin\Program.exe
Build Summary: 3/5 steps succeeded; 1 failed (disable with --summary none)
run transitive failure
+- run Program failure
error: the following build command failed with exit code 1:
C:\...\zig-cache\o\03618dc1d07de7a2b7ce2381e69121e1\build.exe C:\Program Files\zig\zig.exe C:\... C:\...\zig-cache C:\...\AppData\Local\zig run

问题似乎是 Windows 指定了堆分配器,该分配器在运行时支持 ,这意味着错误地初始化了。Zig 一定不能在编译时抱怨表面上的运行时依赖性,因此会出现段错误。ArenaAllocatorcomptime var arena

如何在 comptime 成功初始化竞技场分配器?

也欢迎使用不需要以上述方式初始化分配器的指定分配器的替代方法。comptime

编译时 分配器 zig

评论

2赞 ad absurdum 10/24/2023
你不能在comptime初始化分配器,这并不让我感到惊讶,但编译器似乎应该为此发出错误。
1赞 Cubic 10/25/2023
我认为可能没有办法用内置的 来做到这一点,但应该可以指定一些类似于 comptime 分配器的东西(尽管它必须是全局单例)。Allocator
1赞 sigod 10/28/2023
这对我来说没有意义。如果要消除传递分配器的开销,请使用全局分配器。将分配器作为编译时参数传递...这给了你什么?
0赞 William Ryman 10/28/2023
@sigod 就完整性而言,它没有给我任何东西。但就表达性而言,通过这种语法,我能够表达临时依赖多态性的概念(尽管这只是我问题中讨论的主题),我认为这看起来比使用全局分配器要干净得多。comptime
1赞 Cubic 10/31/2023
@sigod这是关于使用全局分配器,但它允许调用方决定使用哪个全局分配器。因此,如果你在每个模块的基础上使用不同的分配策略,这将是有道理的。

答:

1赞 sigod 11/1/2023 #1

将分配器包装在其自己的全局类型中似乎有效。

const std = @import("std");

const GlobalArena = struct {
    var global_arena: std.heap.ArenaAllocator = undefined;

    fn init(arena: std.heap.ArenaAllocator) void {
        global_arena = arena;
    }

    fn deinit() void {
        global_arena.deinit();
        global_arena = undefined;
    }

    fn allocator() std.mem.Allocator {
        return global_arena.allocator();
    }
};

fn MyTypeGen(comptime GlobalAllocator: type) type {
    return struct {
        var allocator = GlobalAllocator.allocator();

        data: []const u8,

        pub fn init(data: []const u8) !@This() {
            const temp = try allocator.alloc(u8, data.len);
            @memcpy(temp, data);

            return .{ .data = temp };
        }
    };
}

pub fn main() !void {
    GlobalArena.init(std.heap.ArenaAllocator.init(std.heap.page_allocator));
    defer GlobalArena.deinit();

    const MyType = MyTypeGen(GlobalArena);

    const val = try MyType.init(&[_]u8{ 1, 2, 3 });

    std.debug.print("{}\n", .{val});
}
$ zig version
0.12.0-dev.587+eb072fa52

$ zig build run
main.MyTypeGen(main.GlobalArena){ .data = { 1, 2, 3 } }