Указатели и срезы
Разбираем три вида указателей Zig и понимаем, чем срез лучше C-массива.
Срез (slice) — это указатель на начало последовательности плюс её длина; благодаря длине срез знает свои границы, и выход за них ловится, в отличие от голого указателя C.
В C массив при передаче в функцию «разваливается» в указатель и теряет длину — отсюда классические переполнения буфера. Zig решает это, разделяя понятия на несколько чётких типов указателей, и главный из них — срез.
Одиночный указатель
var x: i32 = 10;
const p: *i32 = &x; // указатель на одно значение i32
p.* = 20; // разыменование через .*
// теперь x == 20
Тип *i32 — указатель ровно на одно значение. Разыменование пишется как p.* — постфиксная звёздочка, в отличие от префиксной в C. Адрес берут через &.
Многоуказатель
// [*]T — указатель на неизвестное число элементов
// (аналог голого T* в C, длина неизвестна)
const raw: [*]const u8 = some_c_buffer;
Тип [*]T называется многоуказателем: он указывает на много элементов, но не знает их количества. Это прямой аналог указателя из C, и его используют в основном при взаимодействии с C-кодом. Сам по себе он небезопасен — границы неизвестны.
Срез — безопасный массив с длиной
const std = @import("std");
pub fn main() void {
const numbers = [_]i32{ 5, 10, 15, 20 };
const slice: []const i32 = numbers[1..3]; // элементы 10 и 15
std.debug.print("len={d} first={d}\n", .{ slice.len, slice[0] });
}
Вывод:
len=2 first=10
Тип []T — срез: пара «указатель + длина». Поле slice.len всегда доступно, а доступ slice[i] в safe-сборке проверяется на выход за границу. Срез получают из массива через массив[начало..конец]. Именно срезы — рабочая лошадка Zig для передачи последовательностей в функции.
Как работает под капотом
Физически срез — это структура из двух полей: указатель на первый элемент и количество элементов (usize). Когда вы пишете slice[i] в safe-режиме, компилятор вставляет проверку i < slice.len и роняет программу при нарушении вместо чтения чужой памяти. В ReleaseFast проверка убирается ради скорости. Срез строк — это просто []const u8: последовательность байтов с длиной.
Частые ошибки
Первая — путать *T (один элемент) и [*]T (много элементов): по одиночному указателю нельзя индексироваться. Вторая — использовать p* вместо p.* для разыменования. Третья — забыть, что срез хранит длину, и вручную таскать её отдельной переменной, как в C: это лишнее.
Итог
*T— указатель на одно значение, разыменование черезp.*.[*]T— многоуказатель без длины, аналог C-указателя, небезопасен.[]T— срез: указатель плюс длина, с проверкой границ в safe-сборке.- Срезы — основной способ передавать последовательности в функции.