zig-alloc - 解答

実装コード

FixedBufferAllocator

const std = @import("std");

pub const FixedBufferAllocator = struct {
    buffer: []u8,
    end_index: usize = 0,

    pub fn init(buffer: []u8) FixedBufferAllocator {
        return .{ .buffer = buffer };
    }

    pub fn allocator(self: *FixedBufferAllocator) std.mem.Allocator {
        return .{
            .ptr = self,
            .vtable = &.{
                .alloc = alloc,
                .resize = resize,
                .free = free,
            },
        };
    }

    fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
        _ = ret_addr;
        const self: *FixedBufferAllocator = @ptrCast(@alignCast(ctx));
        const alignment = @as(usize, 1) << @intCast(ptr_align);

        // アライメント調整
        const aligned_index = std.mem.alignForward(usize, self.end_index, alignment);

        if (aligned_index + len > self.buffer.len) {
            return null; // 容量不足
        }

        const result = self.buffer.ptr + aligned_index;
        self.end_index = aligned_index + len;
        return result;
    }

    fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
        _ = ctx;
        _ = buf;
        _ = buf_align;
        _ = new_len;
        _ = ret_addr;
        // 簡易実装: リサイズはサポートしない
        return false;
    }

    fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
        _ = ctx;
        _ = buf;
        _ = buf_align;
        _ = ret_addr;
        // 固定バッファアロケータでは個別解放しない
    }

    pub fn reset(self: *FixedBufferAllocator) void {
        self.end_index = 0;
    }
};

ArenaAllocator

pub const ArenaAllocator = struct {
    child_allocator: std.mem.Allocator,
    state: State,

    const BufNode = struct {
        data: []u8,
        next: ?*BufNode,
    };

    const State = struct {
        buffer_list: ?*BufNode = null,
        end_index: usize = 0,
    };

    pub fn init(child_allocator: std.mem.Allocator) ArenaAllocator {
        return .{
            .child_allocator = child_allocator,
            .state = .{},
        };
    }

    pub fn deinit(self: *ArenaAllocator) void {
        var node = self.state.buffer_list;
        while (node) |n| {
            const next = n.next;
            self.child_allocator.free(n.data);
            self.child_allocator.destroy(n);
            node = next;
        }
        self.state = .{};
    }

    pub fn allocator(self: *ArenaAllocator) std.mem.Allocator {
        return .{
            .ptr = self,
            .vtable = &.{
                .alloc = alloc,
                .resize = resize,
                .free = free,
            },
        };
    }

    fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
        _ = ret_addr;
        const self: *ArenaAllocator = @ptrCast(@alignCast(ctx));

        // 新しいバッファを確保
        const buf_len = @max(len, 4096);
        const buf = self.child_allocator.alloc(u8, buf_len) catch return null;

        // ノードを作成
        const node = self.child_allocator.create(BufNode) catch {
            self.child_allocator.free(buf);
            return null;
        };
        node.* = .{
            .data = buf,
            .next = self.state.buffer_list,
        };
        self.state.buffer_list = node;

        const alignment = @as(usize, 1) << @intCast(ptr_align);
        const aligned_addr = std.mem.alignForward(usize, @intFromPtr(buf.ptr), alignment);

        return @ptrFromInt(aligned_addr);
    }

    fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
        _ = ctx;
        _ = buf;
        _ = buf_align;
        _ = new_len;
        _ = ret_addr;
        return false;
    }

    fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
        _ = ctx;
        _ = buf;
        _ = buf_align;
        _ = ret_addr;
        // アリーナでは個別解放しない
    }
};

LoggingAllocator

pub fn LoggingAllocator(comptime Writer: type) type {
    return struct {
        child_allocator: std.mem.Allocator,
        writer: Writer,
        total_allocated: usize = 0,
        allocation_count: usize = 0,

        const Self = @This();

        pub fn init(child_allocator: std.mem.Allocator, writer: Writer) Self {
            return .{
                .child_allocator = child_allocator,
                .writer = writer,
            };
        }

        pub fn allocator(self: *Self) std.mem.Allocator {
            return .{
                .ptr = self,
                .vtable = &.{
                    .alloc = alloc,
                    .resize = resize,
                    .free = free,
                },
            };
        }

        fn alloc(ctx: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
            const self: *Self = @ptrCast(@alignCast(ctx));

            const result = self.child_allocator.rawAlloc(len, ptr_align, ret_addr);

            self.writer.print("ALLOC: {} bytes, align={}, result={?}\n", .{
                len,
                @as(usize, 1) << @intCast(ptr_align),
                result,
            }) catch {};

            if (result != null) {
                self.total_allocated += len;
                self.allocation_count += 1;
            }

            return result;
        }

        fn resize(ctx: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
            const self: *Self = @ptrCast(@alignCast(ctx));

            const result = self.child_allocator.rawResize(buf, buf_align, new_len, ret_addr);

            self.writer.print("RESIZE: {} -> {} bytes, success={}\n", .{
                buf.len,
                new_len,
                result,
            }) catch {};

            return result;
        }

        fn free(ctx: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
            const self: *Self = @ptrCast(@alignCast(ctx));

            self.writer.print("FREE: {} bytes at {*}\n", .{
                buf.len,
                buf.ptr,
            }) catch {};

            self.total_allocated -= buf.len;
            self.child_allocator.rawFree(buf, buf_align, ret_addr);
        }

        pub fn printStats(self: *Self) void {
            self.writer.print("=== Allocator Stats ===\n", .{}) catch {};
            self.writer.print("Total allocated: {} bytes\n", .{self.total_allocated}) catch {};
            self.writer.print("Allocation count: {}\n", .{self.allocation_count}) catch {};
        }
    };
}

テストコード

const testing = std.testing;

test "FixedBufferAllocator basic" {
    var buffer: [1024]u8 = undefined;
    var fba = FixedBufferAllocator.init(&buffer);
    const alloc = fba.allocator();

    const slice1 = try alloc.alloc(u8, 100);
    try testing.expect(slice1.len == 100);

    const slice2 = try alloc.alloc(u8, 200);
    try testing.expect(slice2.len == 200);

    // バッファをリセット
    fba.reset();

    const slice3 = try alloc.alloc(u8, 500);
    try testing.expect(slice3.len == 500);
}

test "ArenaAllocator basic" {
    var arena = ArenaAllocator.init(testing.allocator);
    defer arena.deinit();

    const alloc = arena.allocator();

    const slice1 = try alloc.alloc(u8, 1000);
    try testing.expect(slice1.len == 1000);

    const slice2 = try alloc.alloc(u32, 100);
    try testing.expect(slice2.len == 100);
}