Виды аллокаторов из стандартной библиотеки
Изучаем готовые аллокаторы стандартной библиотеки и когда какой брать.
Arena-аллокатор выделяет память, но не освобождает её по кусочкам — вся выделенная память возвращается разом в конце; это быстро и удобно для пакетных задач с общим временем жизни.
Раз аллокатор передаётся явно, вызывающий волен выбрать стратегию под задачу. Стандартная библиотека Zig предлагает несколько готовых аллокаторов с разными компромиссами между скоростью, безопасностью и гибкостью.
GeneralPurposeAllocator — для разработки
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit(); // при завершении сообщит об утечках
const alloc = gpa.allocator();
const buf = try alloc.alloc(u8, 16);
defer alloc.free(buf);
}
GeneralPurposeAllocator (часто сокращают до GPA) — универсальный аллокатор для отладки. Его суперспособность — детекция утечек: при deinit он сообщит, если что-то не освобождено, и даже покажет, где это выделили. Это бесценно для поиска утечек ещё на этапе разработки.
ArenaAllocator — выделяй много, освобождай разом
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit(); // одним вызовом освобождает ВСЁ
const alloc = arena.allocator();
const a = try alloc.alloc(u8, 100);
const b = try alloc.alloc(u8, 200);
// не нужно free для a и b — arena.deinit() освободит обоих
Arena-аллокатор идеален, когда множество выделений живут одинаково долго: распарсили файл, поработали, всё выбросили. Вы не освобождаете каждый кусок отдельно — один arena.deinit() возвращает всю память. Это и быстрее (нет учёта каждого блока), и проще (невозможно забыть отдельный free).
FixedBufferAllocator — без кучи вообще
var buffer: [1024]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const alloc = fba.allocator();
// выделяет внутри buffer; куча не задействована
FixedBufferAllocator раздаёт память из заранее выделенного буфера на стеке. Куча не используется вовсе — это критично для встраиваемых систем без динамической памяти. Когда буфер кончается, выделение возвращает ошибку.
Как работает под капотом
Все эти аллокаторы реализуют один и тот же интерфейс std.mem.Allocator, поэтому ваш код их не различает — вы лишь передаёте разный объект. Arena внутри держит список крупных блоков и режет их на запросы, освобождая всё одним махом. GPA ведёт учёт каждого выделения, что и позволяет ловить утечки, но добавляет накладные расходы — поэтому в релизе берут аллокатор быстрее.
Частые ошибки
Первая — использовать GPA в продакшене ради безопасности и терять на его учёте; для релиза берут более быстрый аллокатор. Вторая — звать отдельный free для памяти из арены: это лишнее, всё освободит deinit. Третья — забыть defer gpa.deinit() и не узнать об утечках.
Итог
- Вызывающий выбирает стратегию выделения, передавая нужный аллокатор.
GeneralPurposeAllocatorловит утечки — идеален для разработки.ArenaAllocatorосвобождает всё разом черезdeinit— для общего времени жизни.FixedBufferAllocatorраздаёт память из буфера без обращения к куче.