Allocator インターフェース
『Zig』では、メモリの動的確保は必ず アロケータ(Allocator) を明示的に渡すことで行います。標準ライブラリの多くの関数はアロケータを引数に受け取り、内部で隠れたメモリ確保を行いません。これにより「どこでメモリが確保・解放されるか」が常にコードから読み取れるようになっており、予測可能なメモリ管理と差し替え可能なアロケータ戦略を両立しています。
構文
// -----------------------------------------------
// アロケータの取得と基本的な使い方
// -----------------------------------------------
// GeneralPurposeAllocator — 汎用アロケータです(デバッグ時のリーク検出付き)
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); // プログラム終了時にリーク検査を行います
const allocator = gpa.allocator(); // Allocator インターフェースを取得します
// -----------------------------------------------
// alloc / free — スライス(配列)の確保と解放
// -----------------------------------------------
const slice = try allocator.alloc(T, 個数); // 型 T の配列を確保します(失敗時は error.OutOfMemory)
defer allocator.free(slice); // スコープを抜けると自動で解放されます
// -----------------------------------------------
// create / destroy — 単一要素の確保と解放
// -----------------------------------------------
const ptr = try allocator.create(T); // 型 T 1 個分のメモリを確保してポインタを返します
defer allocator.destroy(ptr); // スコープを抜けると自動で解放されます
// -----------------------------------------------
// ArenaAllocator — まとめて解放できるアロケータです
// -----------------------------------------------
var arena = std.heap.ArenaAllocator.init(親アロケータ);
defer arena.deinit(); // arena.deinit() で全確保分を一括解放します
const arena_alloc = arena.allocator();
// -----------------------------------------------
// アロケータを関数に渡すパターン
// -----------------------------------------------
fn 関数名(allocator: std.mem.Allocator) ![]T {
const buf = try allocator.alloc(T, 個数);
// … 処理 …
return buf; // 呼び出し元が free の責任を持ちます
}
主なアロケータ一覧
| アロケータ | 概要 |
|---|---|
std.heap.GeneralPurposeAllocator | 汎用アロケータです。デバッグビルドではメモリリークや二重解放を検出します。 |
std.heap.ArenaAllocator | 確保は何度でもできますが、解放は『deinit()』による一括解放のみです。短命なデータのまとめ処理に適しています。 |
std.heap.FixedBufferAllocator | スタック上の固定バッファからメモリを切り出します。ヒープを使わないため組み込みや高速処理向きです。 |
std.heap.page_allocator | OS のページ単位でメモリを確保します。小さな確保には向きませんが依存関係が最小です。 |
std.testing.allocator | テスト専用アロケータです。テスト終了時に未解放メモリがあれば自動的に失敗させます。 |
std.mem.Allocator | 全アロケータが実装するインターフェース型です。関数の引数型として使い、アロケータの種類に依存しない設計を実現します。 |
allocator.alloc(T, n) | 型『T』の要素を『n』個確保してスライス『[]T』を返します。失敗時は『error.OutOfMemory』を返します。 |
allocator.free(slice) | スライスを解放します。『alloc』で確保したメモリの返却に使います。 |
allocator.create(T) | 型『T』1 個分のメモリを確保して『*T』を返します。 |
allocator.destroy(ptr) | ポインタが指すメモリを解放します。『create』で確保したポインタの返却に使います。 |
サンプルコード
steinsgate_allocator.zig
// steinsgate_allocator.zig — アロケータの基本サンプルです
// Steins;Gate のキャラクターを使って
// GeneralPurposeAllocator / ArenaAllocator / FixedBufferAllocator の
// 使い分けと defer による自動解放を確認します
const std = @import("std");
// -----------------------------------------------
// ラボメンの名前を受け取って挨拶文を組み立てる関数です
// allocator を引数で受け取るため、呼び出し側がアロケータを自由に選べます
// 返したスライスは呼び出し元が free する責任を持ちます
// -----------------------------------------------
fn buildGreeting(allocator: std.mem.Allocator, name: []const u8, member_no: u8) ![]u8 {
// std.fmt.allocPrint は allocator でメモリを確保してフォーマット済み文字列を返します
return std.fmt.allocPrint(allocator, "ラボメン {d:0>3} 号:{s} — El Psy Kongroo", .{ member_no, name });
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
// -----------------------------------------------
// GeneralPurposeAllocator — 汎用アロケータ
// デバッグビルドではリーク検出が有効になります
// -----------------------------------------------
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); // main 終了時にリーク検査を実行します
const gpa_alloc = gpa.allocator();
try stdout.print("=== 未来ガジェット研究所 ラボメン名簿 ===\n\n", .{});
// 岡部倫太郎(鳳凰院凶真)のメッセージを確保します
const okabe_msg = try buildGreeting(gpa_alloc, "岡部倫太郎(鳳凰院凶真)", 1);
defer gpa_alloc.free(okabe_msg); // スコープ終了時に解放されます
try stdout.print("{s}\n", .{okabe_msg});
// 牧瀬紅莉栖のメッセージを確保します
const kurisu_msg = try buildGreeting(gpa_alloc, "牧瀬紅莉栖", 4);
defer gpa_alloc.free(kurisu_msg);
try stdout.print("{s}\n", .{kurisu_msg});
// -----------------------------------------------
// ArenaAllocator — 一括解放アロケータ
// arena.deinit() を呼ぶだけで確保したメモリをすべて解放できます
// 個別の free は不要なため、短命データのまとめ処理に便利です
// -----------------------------------------------
try stdout.print("\n--- ArenaAllocator でまとめて確保 ---\n", .{});
var arena = std.heap.ArenaAllocator.init(gpa_alloc);
defer arena.deinit(); // ここで arena 内の全メモリを一括解放します
const arena_alloc = arena.allocator();
// ラボメンの名前リストを Arena から確保します(個別 free は不要です)
const members = [_]struct { name: []const u8, no: u8 }{
.{ .name = "椎名まゆり", .no = 2 },
.{ .name = "橋田至(ダル)", .no = 3 },
.{ .name = "桐生萌郁", .no = 5 },
.{ .name = "漆原るか", .no = 6 },
.{ .name = "阿万音鈴羽", .no = 8 },
};
for (members) |m| {
// arena_alloc から確保したスライスは arena.deinit() でまとめて解放されます
const msg = try buildGreeting(arena_alloc, m.name, m.no);
try stdout.print("{s}\n", .{msg});
}
// -----------------------------------------------
// FixedBufferAllocator — スタック上の固定バッファ
// ヒープを使わずスタックバッファからメモリを切り出します
// バッファを超えると error.OutOfMemory が返ります
// -----------------------------------------------
try stdout.print("\n--- FixedBufferAllocator(スタックバッファ 256 bytes)---\n", .{});
var stack_buf: [256]u8 = undefined; // スタック上に 256 バイトのバッファを用意します
var fba = std.heap.FixedBufferAllocator.init(&stack_buf);
const fixed_alloc = fba.allocator();
const amadeus_msg = try buildGreeting(fixed_alloc, "牧瀬紅莉栖(Amadeus)", 99);
// FixedBufferAllocator の free は何もしませんが、インターフェースの一貫性のために呼びます
defer fixed_alloc.free(amadeus_msg);
try stdout.print("{s}\n", .{amadeus_msg});
try stdout.print("使用バイト数: {d} / 256\n", .{fba.end_index});
// -----------------------------------------------
// create / destroy — 単一構造体の確保
// -----------------------------------------------
try stdout.print("\n--- create / destroy で単一構造体を確保 ---\n", .{});
const TimeMachineReading = struct {
divergence: f64, // 世界線変動率(単位: %)
pilot: []const u8, // 操作者の名前
};
// create は型 1 個分のメモリを確保して *T を返します
const reading = try gpa_alloc.create(TimeMachineReading);
defer gpa_alloc.destroy(reading); // スコープ終了時にポインタのメモリを解放します
reading.* = TimeMachineReading{
.divergence = 1.048596,
.pilot = "阿万音鈴羽",
};
try stdout.print("タイムマシン計測値\n", .{});
try stdout.print(" 世界線変動率: {d:.6}%\n", .{reading.divergence});
try stdout.print(" 操作者 : {s}\n", .{reading.pilot});
}
zig run steinsgate_allocator.zig === 未来ガジェット研究所 ラボメン名簿 === ラボメン 001 号:岡部倫太郎(鳳凰院凶真) — El Psy Kongroo ラボメン 004 号:牧瀬紅莉栖 — El Psy Kongroo --- ArenaAllocator でまとめて確保 --- ラボメン 002 号:椎名まゆり — El Psy Kongroo ラボメン 003 号:橋田至(ダル) — El Psy Kongroo ラボメン 005 号:桐生萌郁 — El Psy Kongroo ラボメン 006 号:漆原るか — El Psy Kongroo ラボメン 008 号:阿万音鈴羽 — El Psy Kongroo --- FixedBufferAllocator(スタックバッファ 256 bytes)--- ラボメン 099 号:牧瀬紅莉栖(Amadeus) — El Psy Kongroo 使用バイト数: 55 / 256 --- create / destroy で単一構造体を確保 --- タイムマシン計測値 世界線変動率: 1.048596% 操作者 : 阿万音鈴羽
概要
『Zig』がアロケータを明示的に渡す設計を採用している理由は、「メモリの確保・解放の責任者をコードから一目で分かるようにする」ためです。C の『malloc』/『free』はグローバルに呼び出せるため、どの関数がいつメモリを確保しているかを追うのが困難でした。Zig では関数がアロケータを引数で受け取ることで、呼び出し側が「どのアロケータを使うか」「解放の責任は誰が持つか」を明確に制御できます。また『std.mem.Allocator』というインターフェースを通じて渡すため、本番では『GeneralPurposeAllocator』、テストでは『std.testing.allocator』、組み込みでは『FixedBufferAllocator』と差し替えるだけでコードを変えずに動作を切り替えられます。『defer allocator.free(slice)』を確保の直後に書く慣用句は、解放忘れを防ぐ Zig の定番パターンです。ポインタと『create』/『destroy』の関係は ポインタ *T も合わせて参照してください。
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。