On branch main
modified: src/ch06.md new file: src/ex-ch06-03.zig new file: src/ex-ch06-04.zig
This commit is contained in:
434
src/ch06.md
434
src/ch06.md
@@ -1,5 +1,5 @@
|
||||
|
||||
# Динамическая память и аллокаторы
|
||||
# Динамическая память и распределители памяти
|
||||
|
||||
Всё, с чем имели дело до сих пор, требовало знаний размеров всех сущностей
|
||||
во время компиляции. Массивы всегда имеют размер, известный во время
|
||||
@@ -16,7 +16,7 @@
|
||||
модифицированы), переменные в стеке живут, пока работает функция, который
|
||||
принадлежит этот стековый кадр.
|
||||
|
||||
Эта глава содержит 2 темы. Первая это общий обзор нашей третьей области
|
||||
Эта глава посвящена двум темам. Первая это общий обзор нашей третьей области
|
||||
памяти, кучи. А вот вторая более интересна - в языке Zig имеется вполне
|
||||
ясный, но тем не менее уникальный подход к управлению динамической
|
||||
памятью. Даже если Вы так или иначе умеете работать с кучей
|
||||
@@ -71,15 +71,15 @@ fn getRandomCount() !u8 {
|
||||
}
|
||||
```
|
||||
|
||||
Вскоре мы рассмотрим аллокаторы более подробно, а пока нам нужно знать
|
||||
только то, что `allocator` в этом примере это `std.mem.Allocator`. Мы
|
||||
используем два его метода, `alloc` и `free`. Из того, что мы вызываем
|
||||
`allocator.alloc` при помощи `try`, следует, что этот метод может
|
||||
завершиться с ошибкой. На настоящий момент возможна только одна ошибка,
|
||||
`OutOfMemory`. Параметры этого метода и возвращаемое значение, в
|
||||
принципе, говорят нам о том, как он работает: он требует тип (`T`) и
|
||||
количество элементов, при успешном завершении возвращает полный срез
|
||||
выделенного в куче массива. Выделение памяти происходит во время
|
||||
Вскоре мы рассмотрим распределители памяти (аллокаторы) более подробно, а
|
||||
пока нам нужно знать только то, что `allocator` в этом примере это
|
||||
`std.mem.Allocator`. Мы используем два его метода, `alloc` и `free`. Из
|
||||
того, что мы вызываем `allocator.alloc` при помощи `try`, следует, что
|
||||
этот метод может завершиться с ошибкой. На настоящий момент возможна
|
||||
только одна ошибка, `OutOfMemory`. Параметры этого метода и возвращаемое
|
||||
значение, в принципе, говорят нам о том, как он работает: он требует тип
|
||||
(`T`) и количество элементов, при успешном завершении возвращает полный
|
||||
срез выделенного в куче массива. Выделение памяти происходит во время
|
||||
выполнения, иначе никак, поскольку длина становится известной только во
|
||||
время исполнения.
|
||||
|
||||
@@ -369,24 +369,24 @@ fn init(allocator: std.mem.Allocator, id: u64, power: i32) !*User{
|
||||
|
||||
## Аллокаторы
|
||||
|
||||
Одним из базовых принципов Zig является принцип "никаких скрытых выделений памяти".
|
||||
В зависимости от Вашего предыдущего опыта программирования,
|
||||
возможно, для Вас в этом принципе нет ничего особенного.
|
||||
Тем не менее, это сильно контрастирует с тем, что мы имеем в стандартной
|
||||
библиотеке языка C, а там для выделения памяти мы имеем `malloc`.
|
||||
В C для того, чтобы понять, использует ли та или иная функция кучу,
|
||||
нужно глядеть в исходный код этой функции и искать там `malloc`
|
||||
и родственные ей, например, `calloc`.
|
||||
Одним из базовых принципов Zig является принцип "никаких скрытых
|
||||
выделений памяти". В зависимости от Вашего предыдущего опыта
|
||||
программирования, возможно, для Вас в этом принципе нет ничего
|
||||
особенного. Тем не менее, это сильно контрастирует с тем, что мы имеем в
|
||||
стандартной библиотеке языка C, а там для выделения памяти мы имеем
|
||||
`malloc`. В C для того, чтобы понять, использует ли та или иная функция
|
||||
кучу, нужно глядеть в исходный код этой функции и искать там `malloc` и
|
||||
родственные ей, например, `calloc`.
|
||||
|
||||
А вот в Zig нет аллокатора "по умолчанию". Во всех примерах выше
|
||||
функции, которые размещают сущности в куче, принимают в качестве
|
||||
параметра `std.mem.Allocator`. По соглашению, обычно это первый параметр.
|
||||
Вся стандартная библиотека Zig, а также большинство сторонних библиотек
|
||||
требуют, чтобы вызывающая сторона передала аллокатор в случае,
|
||||
если этим библиотекам требуется выделять память динамически.
|
||||
А вот в Zig нет аллокатора "по умолчанию". Во всех примерах выше функции,
|
||||
которые размещают сущности в куче, принимают в качестве параметра
|
||||
`std.mem.Allocator`. По соглашению, обычно это первый параметр. Вся
|
||||
стандартная библиотека Zig, а также большинство сторонних библиотек
|
||||
требуют, чтобы вызывающая сторона передала аллокатор в случае, если этим
|
||||
библиотекам требуется выделять память динамически.
|
||||
|
||||
Такое явное указание аллокаторов может принимать две формы.
|
||||
В простых случаях, аллокатор передаётся каждой функции, например:
|
||||
Такое явное указание аллокаторов может принимать две формы. В простых
|
||||
случаях, аллокатор передаётся каждой функции, например:
|
||||
|
||||
```zig
|
||||
onst say = std.fmt.allocPrint(allocator, "It's over {d}!!!", .{user.power});
|
||||
@@ -394,41 +394,365 @@ defer allocator.free(say);
|
||||
```
|
||||
|
||||
Функция `std.fmt.allocPrint` похожа на `std.debug.print`, только вместо
|
||||
того, чтобы печатать в `stderr`, она выделяет память под строку и "печатает"
|
||||
в эту строку (как `sprintf` в C).
|
||||
того, чтобы печатать в `stderr`, она выделяет память под строку и
|
||||
"печатает" в эту строку (как `sprintf` в C).
|
||||
|
||||
Другая форма это когда аллокатор передаётся в "конструктор" (`init`),
|
||||
там запоминается и далее используется для внутренних нужд объекта.
|
||||
Мы видели такую форму выше, в реализации структуры `Game`.
|
||||
Такая форма менее явна, потому что Вы знаете, что объект будет
|
||||
использовать динамическую память, но не знаете, в каких именно своих
|
||||
методах он будет это делать. Такой подход более практичен для долго живущих сущностей.
|
||||
Другая форма это когда аллокатор передаётся в "конструктор" (`init`), там
|
||||
запоминается и далее используется для внутренних нужд объекта. Мы видели
|
||||
такую форму выше, в реализации структуры `Game`. Такая форма менее явна,
|
||||
потому что Вы знаете, что объект будет использовать динамическую память,
|
||||
но не знаете, в каких именно своих методах он будет это делать. Такой
|
||||
подход более практичен для долго живущих сущностей.
|
||||
|
||||
Преимущество "внедрения" аллокатора состоит не только в том, что
|
||||
явно обозначено, что функция/объект будут использовать кучу,
|
||||
но также и в том, что это предоставляет некоторую гибкость.
|
||||
Дело в том, что `std.mem.Allocator` это *интерфейс* (а не конкретный аллокатор),
|
||||
который предоставляет методы `alloc/free` и `create/destroy`, наряду с некоторыми другими.
|
||||
До этого момента мы видели только `std.heap.GeneralPurposeAllocator`,
|
||||
но в стандартной библиотеке имеются реализации и других аллокаторов.
|
||||
Преимущество "внедрения" аллокатора состоит не только в том, что явно
|
||||
обозначено, что функция/объект будут использовать кучу, но также и в том,
|
||||
что это предоставляет некоторую гибкость. Дело в том, что
|
||||
`std.mem.Allocator` это *интерфейс* (а не конкретный аллокатор), который
|
||||
предоставляет методы `alloc/free` и `create/destroy`, наряду с некоторыми
|
||||
другими. До этого момента мы видели только
|
||||
`std.heap.GeneralPurposeAllocator`, но в стандартной библиотеке имеются
|
||||
реализации и других аллокаторов.
|
||||
|
||||
Если Вы разрабатываете библиотеку, лучше всего будет, если она будет
|
||||
принимать аллокатор, это позволит пользователям Вашей библиотеки
|
||||
сами выбирать, какой конкретно аллокатор будет использоваться.
|
||||
В противном случае Вам будет нужно выбирать "правильный" аллокатор и,
|
||||
как мы далее увидим, аллокаторы не являются взаимоисключающими -
|
||||
программа может одновременно использовать несколько видов аллокаторов,
|
||||
и на то могут быть свои причины.
|
||||
|
||||
|
||||
принимать аллокатор, это позволит пользователям Вашей библиотеки сами
|
||||
выбирать, какой конкретно аллокатор будет использоваться. В противном
|
||||
случае Вам будет нужно выбирать "правильный" аллокатор и, как мы далее
|
||||
увидим, аллокаторы не являются взаимоисключающими - программа может
|
||||
одновременно использовать несколько видов аллокаторов, и на то могут быть
|
||||
свои весьма веские причины.
|
||||
|
||||
## Аллокатор общего назначения
|
||||
|
||||
## `std.testing.allocator`
|
||||
Как и подразумевает само название этого аллокатора
|
||||
(`std.heap.GeneralPurposeAllocator`), это во всех отношениях аллокатор
|
||||
общего назначения, в частности, он безопасен для использования в
|
||||
многопоточных программах и может служить главным аллокатором в Ваших
|
||||
программах. Для большинства программ он будет единственным аллокатором.
|
||||
Аллокатор создаётся на старте программы и затем передаётся тем функциям,
|
||||
которым он нужен. Вот небольшой пример:
|
||||
|
||||
```zig
|
||||
const std = @import("std");
|
||||
const httpz = @import("httpz");
|
||||
|
||||
pub fn main() !void {
|
||||
// create our general purpose allocator
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
|
||||
// get an std.mem.Allocator from it
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
// pass our allocator to functions and libraries that require it
|
||||
var server = try httpz.Server().init(allocator, .{.port = 5882});
|
||||
|
||||
var router = server.router();
|
||||
router.get("/api/user/:id", getUser);
|
||||
|
||||
// blocks the current thread
|
||||
try server.listen();
|
||||
}
|
||||
```
|
||||
|
||||
Здесь мы создаём `GeneralPurposeAllocator`, получаем из него
|
||||
`std.mem.Allocator` и заьем передаём его функции `init` структуры
|
||||
`httpz.Server`. В более сложном проекте аллокатор, скорей всего, будет
|
||||
передаваться во многие другие подсистемы, каждая из которых, в свою
|
||||
очередь, будет передавать аллокатор дальше по цепочке для своих функций,
|
||||
объектов и т.п.
|
||||
|
||||
Наверное, Вы обратили внимание, что синтаксис создания аллокатора
|
||||
какой-то странный. Что вообще такое `GeneralPurposeAllocator(.{}){}`? На
|
||||
самом деле всё это уже нам знакомо, просто тут оно сразу всё в кучу.
|
||||
`GeneralPurposeAllocator` это, очевидно, функция и, поскольку она
|
||||
написана в `PascalCase`, она возвращает *тип* (мы будем говорить об
|
||||
обощённых структурахданных в следующей главе). Зная, что она возвращает
|
||||
тип, этот пример, где это явно обозначеноЮ будет легче "расшифровать":
|
||||
|
||||
```zig
|
||||
const T = std.heap.GeneralPurposeAllocator(.{});
|
||||
var gpa = T{};
|
||||
|
||||
// is the same as:
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
```
|
||||
|
||||
Возможно, Вы всё ещё не уверены в смысле `.{}`. Это мы тоже встречали
|
||||
раньше, это инициализатор структуры с неявным типом, тип будет выведен
|
||||
компилятором. А что это за тип и какие у него поля? Это
|
||||
`std.heap.general_purpose_allocator.Config`, хоть это явно у нас и не
|
||||
обозначено. Никакие поля тут мы не инициализируем, потому что в `Config`
|
||||
указаны значения по умолчанию, их мы и намеревались использовать. Это
|
||||
общая практика при работе с конфигурациями/настройками. Несколькими
|
||||
строчками ниже мы встречаем подобное ещё раз, при передаче `.{.port =
|
||||
5882}` функции `Server.init`. В данном случае мы используем значения по
|
||||
умолчанию для всех полей, кроме поля `port`.
|
||||
|
||||
## Аллокатор для тестирования
|
||||
|
||||
Автор выражает надежду, что Вы серьезно призадумались, когда мы обсуждали
|
||||
утечки памяти и после заявления про то, что Zig тут может помочь,
|
||||
захотели узнать, чем именно. Помощь нам тут может оказать
|
||||
`std.testing.allocator`, который на данный момент реализовать на основе
|
||||
`GeneralPurposeAllocator`, но с добавлением интеграции с системой тестов
|
||||
Zig, но это детали реализации. Нам будет важно то, что если мы будем
|
||||
использовать `std.testing.allocator` а наших тестах, он нам отловит
|
||||
большинство утечек памяти, если таковые будут.
|
||||
|
||||
Наверняка Вы уже знакомы с динамическими массивами, также часто называемых `ArrayList`.
|
||||
Во многих языках с динамической типизацией вообще всё массивы динамические.
|
||||
Динамические массивы могут менять свою длину в процессе работы программы.
|
||||
В стандартной библиотеке Zig есть реализация обобщённого динамического массива,
|
||||
но мы сейчас вручную сделаем реализацию специализированного (только для целых)
|
||||
динамического массива и продемонстрируем детекцию утечки памяти:
|
||||
|
||||
```zig
|
||||
pub const IntList = struct {
|
||||
pos: usize,
|
||||
items: []i64,
|
||||
allocator: Allocator,
|
||||
|
||||
fn init(allocator: Allocator) !IntList {
|
||||
return .{
|
||||
.pos = 0,
|
||||
.allocator = allocator,
|
||||
.items = try allocator.alloc(i64, 4),
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: IntList) void {
|
||||
self.allocator.free(self.items);
|
||||
}
|
||||
|
||||
fn add(self: *IntList, value: i64) !void {
|
||||
const pos = self.pos;
|
||||
const len = self.items.len;
|
||||
|
||||
if (pos == len) {
|
||||
// we've run out of space
|
||||
// create a new slice that's twice as large
|
||||
var larger = try self.allocator.alloc(i64, len * 2);
|
||||
|
||||
// copy the items we previously added to our new space
|
||||
@memcpy(larger[0..len], self.items);
|
||||
|
||||
self.items = larger;
|
||||
}
|
||||
|
||||
self.items[pos] = value;
|
||||
self.pos = pos + 1;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Нас тут будет интересовать та часть функции `add`, где проверяется, а не
|
||||
переполнится ли наш массив при добавлении в него очередного элемента,
|
||||
далее при необходимости длина массива увеличивается в два раза и только
|
||||
после этого добавляется новый элемент. Мы можем использовать наш
|
||||
`IntList` вот так:
|
||||
|
||||
```zig
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var list = try IntList.init(allocator);
|
||||
defer list.deinit();
|
||||
|
||||
for (0..10) |i| {
|
||||
try list.add(@intCast(i));
|
||||
}
|
||||
|
||||
std.debug.print("{any}\n", .{list.items[0..list.pos]});
|
||||
}
|
||||
```
|
||||
|
||||
Код работает правильно и печатает то, что от него ожидается. Однако,
|
||||
несмотря на то, что мы вызвали `deinit`, тут имеет место утечка. Если Вы
|
||||
не заметили, где она происходит, ничего страшного, ведь сейчас мы напишем
|
||||
тест и будем в нём использовать `std.testing.allocator`:
|
||||
|
||||
```zig
|
||||
const testing = std.testing;
|
||||
test "IntList: add" {
|
||||
// We're using testing.allocator here!
|
||||
var list = try IntList.init(testing.allocator);
|
||||
defer list.deinit();
|
||||
|
||||
for (0..5) |i| {
|
||||
try list.add(@intCast(i+10));
|
||||
}
|
||||
|
||||
try testing.expectEqual(@as(usize, 5), list.pos);
|
||||
try testing.expectEqual(@as(i64, 10), list.items[0]);
|
||||
try testing.expectEqual(@as(i64, 11), list.items[1]);
|
||||
try testing.expectEqual(@as(i64, 12), list.items[2]);
|
||||
try testing.expectEqual(@as(i64, 13), list.items[3]);
|
||||
try testing.expectEqual(@as(i64, 14), list.items[4]);
|
||||
}
|
||||
```
|
||||
|
||||
Тесты в Zig обычно пишутся в том же файле, где реализуется то,
|
||||
что мы хотим протестировать. Поместите 3 предыдущих отрывка кода в один файл
|
||||
и запустите тест, используя команду `zig test src/ex-ch06-03.zig`
|
||||
и наслаждайтесь результатом:
|
||||
|
||||
```
|
||||
$ /opt/zig-0.11/zig test src/ex-ch06-03.zig
|
||||
Test [1/1] test.IntList: add... [gpa] (err): memory address 0x7ff7897f4000 leaked:
|
||||
/coding/zig-lang/learning-zig-rus/src/ex-ch06-03.zig:14:41: 0x2248be in init (test)
|
||||
.items = try allocator.alloc(i64, 2),
|
||||
... ^
|
||||
|
||||
[gpa] (err): memory address 0x7ff7897fe000 leaked:
|
||||
/home/zed/2-coding/zig-lang/learning-zig-rus/src/ex-ch06-03.zig:29:50: 0x224cd0 in add (test)
|
||||
var larger = try self.allocator.alloc(i64, len * 2);
|
||||
^
|
||||
...
|
||||
All 1 tests passed.
|
||||
2 errors were logged.
|
||||
1 tests leaked memory.
|
||||
```
|
||||
|
||||
У нас тут аж 2 утечки! К счастью, тестирующий аллокатор говорит нам в точности,
|
||||
где была выделена память, указатели на которую мы не освободили.
|
||||
Можете теперь отследить, где и как происходит утечка? Если нет, вспомните,
|
||||
что каждому выделению памяти (`alloc`) должно соответствовать её освобождение (`free`).
|
||||
Наш код вызывает `free` только один раз, в функции `deinit`, однако, `alloc`
|
||||
вызывается более одного раза - первый раз при создании списка, то есть в функции `init`
|
||||
и затем всякий раз, когда вызывается метод `add` и нам нужно расширить массив.
|
||||
Поэтому всякий раз, когда мы перевыделяем память под массив, нам нужно
|
||||
освободить память от предыдущего (более короткого) массива:
|
||||
|
||||
```zig
|
||||
// existing code
|
||||
var larger = try self.allocator.alloc(i64, len * 2);
|
||||
@memcpy(larger[0..len], self.items);
|
||||
|
||||
// Added code
|
||||
// free the previous allocation
|
||||
self.allocator.free(self.items);
|
||||
```
|
||||
|
||||
Добавление строчки `self.allocator.free(self.items);` после копирования массива
|
||||
в новую область решает нашу проблему. Теперь тест завершается успешно.
|
||||
|
||||
## Распределитель на основе регионов
|
||||
|
||||
Распределитель памяти общего назначения, то есть `GeneralPurposeAllocator` это
|
||||
вполне разумный выбор по умолчанию, поскольку он в среднем хорошо работает
|
||||
для любых возможных сценариев и объёмов выделяемой,высвобождаемой памяти.
|
||||
Однако, в какой-то конкретной программе Вы можете столкнуться с такой
|
||||
схемой выделения/освобождения, для которой будет более выгодно использовать
|
||||
специализированные аллокаторы. В качестве примера можно привести ситуацию,
|
||||
когда Вам нужны какие-то относительно короткоживущие данные,
|
||||
которые хотелось бы удалить, так сказать, одним махом. Часто под такие
|
||||
требования подпадают парсеры. Вот скелет некой функции разбора каких-то данных:
|
||||
|
||||
```zig
|
||||
fn parse(allocator: Allocator, input: []const u8) !Something {
|
||||
var state = State{
|
||||
.buf = try allocator.alloc(u8, 512),
|
||||
.nesting = try allocator.alloc(NestType, 10),
|
||||
};
|
||||
defer allocator.free(state.buf);
|
||||
defer allocator.free(state.nesting);
|
||||
|
||||
return parseInternal(allocator, state, input);
|
||||
}
|
||||
```
|
||||
|
||||
Хоть с этим и не особо сложно управляться, однако, функция
|
||||
`parseInternal`, возможно, сама используется много сущностей с коротким
|
||||
временем жизни, которые, разумеется, тоже надо удалять. И было бы
|
||||
замечательно, если бы могли освободить память от множества объектов не
|
||||
вызывая много раз `free` или `destroy`, а как бы за один присест. А тут
|
||||
нам на помощь приходит `ArenaAllocator`:
|
||||
|
||||
```zig
|
||||
fn parse(allocator: Allocator, input: []const u8) !Something {
|
||||
// create an ArenaAllocator from the supplied allocator
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
|
||||
// this will free anything created from this arena
|
||||
defer arena.deinit();
|
||||
|
||||
// create an std.mem.Allocator from the arena, this will be
|
||||
// the allocator we'll use internally
|
||||
const aa = arena.allocator();
|
||||
|
||||
var state = State{
|
||||
// we're using aa here!
|
||||
.buf = try aa.alloc(u8, 512),
|
||||
|
||||
// we're using aa here!
|
||||
.nesting = try aa.alloc(NestType, 10),
|
||||
};
|
||||
|
||||
// we're passing aa here, so any we're guaranteed that
|
||||
// any other allocation will be in our arena
|
||||
return parseInternal(aa, state, input);
|
||||
}
|
||||
```
|
||||
|
||||
Этот аллокатор принимает "дочерний" аллокатор, в данном случае это
|
||||
аллокатор, который передаётся в функцию, которая создаёт другой
|
||||
аллокатор, `ArenaAllocator`. При использовании этого аллокатора нам не
|
||||
нужно чистить память индивидуально для каждой сущности, размещённой на
|
||||
"арене": всё будет освобождено разом при вызове `arena.deinit`. Методы
|
||||
`free` и `destroy` в этом аллокаторе всё же имеются, но они просто ничего
|
||||
не делают.
|
||||
|
||||
Аллокатор на основе регионов следует использовать с осторожностью.
|
||||
Поскольку здесь нет никакого способа высвобождать память для каждой
|
||||
сущности отдельно, нужно иметь уверенность, что `arena.deinit` будет
|
||||
вызвана прежде, чем мы задействуем слишком много памяти. Интересно, что
|
||||
это знание (когда нужно почистить) может содержаться как внутри, так и
|
||||
снаружи функции/объекта? Например, в приведённом выше скелете
|
||||
представляется разумным задействовать `ArenaAllocator` внутри `Parser`,
|
||||
поскольку детали, касающиеся времён жизни - это сугубо "личное дело"
|
||||
самого парсера.
|
||||
|
||||
Однако, этого нельзя сказать про наш список `intList`. Он может
|
||||
быть использован для хранения 10 чисел, а может быть использован
|
||||
для хранения 10 миллионов чисел. Кроме того, список может быть нужен
|
||||
на протяжении, скажем, 100 миллисекунд, а может и на протяжении недель.
|
||||
То есть сам список никак не может знать, как аллокатор выгоднее использовать,
|
||||
такое знанием в данном случае обладает код, который использует список.
|
||||
Изначально мы управлялись (в плане аллокатора) с нашим списком вот так:
|
||||
|
||||
```zig
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var list = try IntList.init(allocator);
|
||||
defer list.deinit();
|
||||
```
|
||||
|
||||
Возможно, нам по каким-то причинам захотелось использовать аллокатор на основе регионов:
|
||||
|
||||
```zig
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
defer arena.deinit();
|
||||
const aa = arena.allocator();
|
||||
|
||||
var list = try IntList.init(aa);
|
||||
|
||||
// I'm honestly torn on whether or not we should call list.deinit.
|
||||
// Technically, we don't have to since we call defer arena.deinit() above.
|
||||
defer list.deinit();
|
||||
```
|
||||
|
||||
При этом нам не нужно вносить изменения в реализацию `intList`, он работает
|
||||
только с `std.mem.Allocator`, то есть с интерфейсом, общим для всех аллокаторов.
|
||||
И если бы где-то в `intList` создавался бы ещё один аллокатор с регионами,
|
||||
то это тоже бы сработало, поскольку никто не запрещает иметь арену внутри арены.
|
||||
|
||||
## Arena...
|
||||
|
||||
## FixedBufferAllocator
|
||||
|
||||
|
||||
|
||||
|
||||
73
src/ex-ch06-03.zig
Normal file
73
src/ex-ch06-03.zig
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub const IntList = struct {
|
||||
pos: usize,
|
||||
items: []i64,
|
||||
allocator: Allocator,
|
||||
|
||||
fn init(allocator: Allocator) !IntList {
|
||||
return .{
|
||||
.pos = 0,
|
||||
.allocator = allocator,
|
||||
.items = try allocator.alloc(i64, 2),
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: IntList) void {
|
||||
self.allocator.free(self.items);
|
||||
}
|
||||
|
||||
fn add(self: *IntList, value: i64) !void {
|
||||
const pos = self.pos;
|
||||
const len = self.items.len;
|
||||
|
||||
if (pos == len) {
|
||||
// we've run out of space
|
||||
// create a new slice that's twice as large
|
||||
var larger = try self.allocator.alloc(i64, len * 2);
|
||||
|
||||
// copy the items we previously added to our new space
|
||||
@memcpy(larger[0..len], self.items);
|
||||
|
||||
self.items = larger;
|
||||
}
|
||||
|
||||
self.items[pos] = value;
|
||||
self.pos = pos + 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var list = try IntList.init(allocator);
|
||||
defer list.deinit();
|
||||
|
||||
for (0..10) |i| {
|
||||
try list.add(@intCast(i));
|
||||
}
|
||||
|
||||
std.debug.print("{any}\n", .{list.items[0..list.pos]});
|
||||
}
|
||||
|
||||
const testing = std.testing;
|
||||
test "IntList: add" {
|
||||
// We're using testing.allocator here!
|
||||
var list = try IntList.init(testing.allocator);
|
||||
defer list.deinit();
|
||||
|
||||
for (0..5) |i| {
|
||||
try list.add(@intCast(i+10));
|
||||
}
|
||||
|
||||
try testing.expectEqual(@as(usize, 5), list.pos);
|
||||
try testing.expectEqual(@as(i64, 10), list.items[0]);
|
||||
try testing.expectEqual(@as(i64, 11), list.items[1]);
|
||||
try testing.expectEqual(@as(i64, 12), list.items[2]);
|
||||
try testing.expectEqual(@as(i64, 13), list.items[3]);
|
||||
try testing.expectEqual(@as(i64, 14), list.items[4]);
|
||||
}
|
||||
73
src/ex-ch06-04.zig
Normal file
73
src/ex-ch06-04.zig
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub const IntList = struct {
|
||||
pos: usize,
|
||||
items: []i64,
|
||||
allocator: Allocator,
|
||||
|
||||
fn init(allocator: Allocator) !IntList {
|
||||
return .{
|
||||
.pos = 0,
|
||||
.allocator = allocator,
|
||||
.items = try allocator.alloc(i64, 2),
|
||||
};
|
||||
}
|
||||
|
||||
fn deinit(self: IntList) void {
|
||||
self.allocator.free(self.items);
|
||||
}
|
||||
|
||||
fn add(self: *IntList, value: i64) !void {
|
||||
const pos = self.pos;
|
||||
const len = self.items.len;
|
||||
|
||||
if (pos == len) {
|
||||
// we've run out of space
|
||||
// create a new slice that's twice as large
|
||||
var larger = try self.allocator.alloc(i64, len * 2);
|
||||
|
||||
// copy the items we previously added to our new space
|
||||
@memcpy(larger[0..len], self.items);
|
||||
self.allocator.free(self.items);
|
||||
self.items = larger;
|
||||
}
|
||||
|
||||
self.items[pos] = value;
|
||||
self.pos = pos + 1;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var list = try IntList.init(allocator);
|
||||
defer list.deinit();
|
||||
|
||||
for (0..10) |i| {
|
||||
try list.add(@intCast(i));
|
||||
}
|
||||
|
||||
std.debug.print("{any}\n", .{list.items[0..list.pos]});
|
||||
}
|
||||
|
||||
const testing = std.testing;
|
||||
test "IntList: add" {
|
||||
// We're using testing.allocator here!
|
||||
var list = try IntList.init(testing.allocator);
|
||||
defer list.deinit();
|
||||
|
||||
for (0..5) |i| {
|
||||
try list.add(@intCast(i+10));
|
||||
}
|
||||
|
||||
try testing.expectEqual(@as(usize, 5), list.pos);
|
||||
try testing.expectEqual(@as(i64, 10), list.items[0]);
|
||||
try testing.expectEqual(@as(i64, 11), list.items[1]);
|
||||
try testing.expectEqual(@as(i64, 12), list.items[2]);
|
||||
try testing.expectEqual(@as(i64, 13), list.items[3]);
|
||||
try testing.expectEqual(@as(i64, 14), list.items[4]);
|
||||
}
|
||||
Reference in New Issue
Block a user