On branch main
modified: src/ch06.md new file: src/ch07.md new file: src/ch08.md
This commit is contained in:
111
src/ch06.md
111
src/ch06.md
@@ -1,10 +1,121 @@
|
||||
|
||||
# Динамическая память и аллокаторы
|
||||
|
||||
Всё, счем имели дело до сих пор, требовало знаний размеров всех сущностей
|
||||
во время компиляции. Массивы всегда имеют размер, известный во время
|
||||
компиляции (фактически, длина массива является частью типа). Все наши
|
||||
строки были строковыми литералами, длина которых тоже понятна по
|
||||
исходному тексту.
|
||||
|
||||
Далее, две стратегии управления памятью (глобальные данные и стек),
|
||||
которые мы видели, хоть и являются простыми и эффективными, всё же
|
||||
ограничивают наши возможности. Обе они никак не могут помочь в управлении
|
||||
данными, которые могут менять размер во время исполнения и, кроме того,
|
||||
не обладают необходимой гибкостью в плане времён жизни - глобальные
|
||||
данные живут всё время работы программы (и при этом не могут быть
|
||||
модифицированы), переменные в стеке живут, пока работает функция, который
|
||||
принадлежит этот стековый кадр.
|
||||
|
||||
Эта главв содержит 2 темы. Первая это общий обзор нашей третьей области
|
||||
памяти, кучи. А вот вторая более интересна - в языке Zig имеется вполне
|
||||
ясный, но тем не менее уникальный подход к управлению динамической
|
||||
памятью. Даже если Вы так или иначе умеете работать с кучей
|
||||
(например, при помощи `malloc` и `free` из стандартной библиотеки языка C),
|
||||
Вам всё равно следует проштудировать эту главу, поскольку в Zig
|
||||
есть свои особенности.
|
||||
|
||||
## Динамическая память (heap, "куча")
|
||||
|
||||
Куча это наша третья область памяти в нашем распоряжении.
|
||||
В сравнении с первыми двумя это немного "дикий запад": дозволено всё.
|
||||
Именно, мы можем создавать сущности в куче с размером,
|
||||
известным только во время исполнения программы и при этом
|
||||
имеем полный контроль над временем жизни этих сущностей.
|
||||
|
||||
Стек вызовов это замечательная штука ввиду простоты и предсказуемости
|
||||
способа, которым он управляет данными (создание/удаление стековых
|
||||
кадров). С другой стороны, эта простота одновременно является
|
||||
недостатком: времена жизни переменных в стеке жёстко привязяны ко времени
|
||||
жизни самого стекового кадра, в котором они "живут". Куча это полная
|
||||
противоположность этому. Для объектов в куче нет никакого встроенного
|
||||
механизма контроля времени жизни, поэтому эти объекты могут существовать
|
||||
столь долго или, наоборот, столь коротко, сколько мы захотим. И эти
|
||||
преимущества, опять же, палка о двух концах: если мы не освободим память,
|
||||
никто её за нас не освободит.
|
||||
|
||||
Давайте посмотрим на следующий пример:
|
||||
|
||||
```zig
|
||||
const std = @import("std");
|
||||
|
||||
pub fn main() !void {
|
||||
// we'll be talking about allocators shortly
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
// ** The next two lines are the important ones **
|
||||
var arr = try allocator.alloc(usize, try getRandomCount());
|
||||
defer allocator.free(arr);
|
||||
|
||||
for (0..arr.len) |i| {
|
||||
arr[i] = i;
|
||||
}
|
||||
std.debug.print("{any}\n", .{arr});
|
||||
}
|
||||
|
||||
fn getRandomCount() !u8 {
|
||||
var seed: u64 = undefined;
|
||||
try std.os.getrandom(std.mem.asBytes(&seed));
|
||||
var random = std.rand.DefaultPrng.init(seed);
|
||||
return random.random().uintAtMost(u8, 5) + 5;
|
||||
}
|
||||
```
|
||||
|
||||
Вскоре мы рассмотрим аллокаторы более подробно, а пока нам нужно знать
|
||||
только то, что `allocator` в этом примере это `std.mem.Allocator`. Мы
|
||||
используем два его метода, `alloc` и `free`. Из того, что мы вызываем
|
||||
`allocator.alloc` при помощи `try`, следует, что этот метод может
|
||||
завершиться с ошибкой. На настоящий момент возможна только одна ошибка,
|
||||
`OutOfMemory`. Параметры этого метода и возвращаемое значение, в
|
||||
принципе, говорят нам о том, как он работает: он требует тип (`T`) и
|
||||
количество элементов, при успешном завершении возвращает полный срез
|
||||
выделенного в куче массива. Выделение памяти происходит во время
|
||||
выполнения, иначе никак, поскольку длина становится известной только во
|
||||
время исполнения.
|
||||
|
||||
Общее правило при динамическом выделении памяти - каждый вызов `alloc`,
|
||||
как правило, имеет соответствующий вызов `free`. Первый выделяет память,
|
||||
второй освобождает. Однако, не дайте этому примеру ограничить Ваше
|
||||
воображение. Да, этот паттерн, `try alloc` и сразу следом `defer free`
|
||||
действительно часто испольузется, и не зря: такое размещение инструкций
|
||||
является относительно надёжной защитой от ошибок в виде того, что мы
|
||||
"забыли" освободить. Но так же часто используется совершенно иная схема:
|
||||
выделение памяти делается в одном месте, а освобождение совершенно в
|
||||
другом (например, в другой функции, в другом методе или даже в другой
|
||||
нити). Как мы уже отметили, нет никаких автоматических механизмов
|
||||
управления временем жизни объектов в куче, всё в нашей власти.
|
||||
Представьте себе веб-сервер с постадийной обработкой запроса, причём
|
||||
каждую стадию отрабатывает выделенная для этого нить - тогда память может
|
||||
быть выделена на стадии получения HTTP-запроса, а освождена где-то на
|
||||
конечной стадии, например, при записи в журнал, в котором фиксируются все
|
||||
запросы.
|
||||
|
||||
## `defer` и `errdefer`
|
||||
|
||||
В рассматриваемом примере мы ввели в обиход незнакомую нам до этого новую
|
||||
особенность языка Zig, `defer`. Этот механизм, согласно его названию,
|
||||
откладывает исполнение обозначенного кода (который сам по себе может быть
|
||||
блоком) до того момента, когда закончится выполняться блок (включая явный
|
||||
`return`), в котором находится `defer`. Такое отложенное выполнение вовсе
|
||||
не связано конкретно с аллокаторами и управлением памятью, это более
|
||||
общий механизм, Вы можете использовать его для выполнения абсолютно
|
||||
произвольного кода.
|
||||
|
||||
`defer` в Zig подобен таковому в `Go`, с одним важным отличием:
|
||||
в Zig отложенные инструкции будут выполнены по завершении
|
||||
*блока*, где они заданы, а в Go они будут выполнены в конце *функции*.
|
||||
|
||||
|
||||
## Повторное освобождение и утечки памяти
|
||||
|
||||
## `create` и `destroy`
|
||||
|
||||
3
src/ch07.md
Normal file
3
src/ch07.md
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
# Обобщённые струкутры данных
|
||||
|
||||
3
src/ch08.md
Normal file
3
src/ch08.md
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
# Программируем на языке Zig
|
||||
|
||||
Reference in New Issue
Block a user