mem: распределение, перераспределение и макет.

Описывает размер в байтах, ошибки выделения, границы перераспределения и обязанности по освобождению.

Wave Foundation

Распределение и освобождение

std::mem::alloc
mem_alloc(size: i64) -> ptr<u8>
mem_alloc_zeroed(size: i64) -> ptr<u8>
mem_free(p: ptr<u8>, size: i64) -> i64
mem_realloc(old_ptr: ptr<u8>, old_size: i64, new_size: i64) -> ptr<u8>

Размер указан в байтах. Распределение размера 0 или меньше возвращает null. Если выделение также не удается для положительных размеров, это может быть null. Не предполагайте исходное содержимое mem_alloc, но используйте mem_alloc_zeroed, если требуется нулевая инициализация.

Вызывающий объект владеет каждым успешным выделением и должен передать его первоначальный размер при его освобождении. mem_free(null, size) возвращает 0. Указатель, отличный от null, в сочетании с неположительным размером является ошибкой. Никогда не открывайте и не освобождайте выделение после того, как оно уже освобождено.

Классификация в случае перераспределения

запрос действие
новый размер – это позитив и успех min(old_size, new_size) Копировать байт и освободить предыдущий.
Новое распределение положительного размера не удалось null Вернуться, сохранить существующее распределение.
old_ptr == null, новый положительный размер ведет себя как новое задание
new_size == 0 Попытка освободить допустимое предыдущее выделение и возврат null
old_size=0 для отрицательного размера или существующего указателя null Возврат

Результат null перераспределения до нулевого размера не определяет, удалось ли освободить. Если вам нужен его статус, позвоните напрямую по номеру mem_free. После увеличения выделения самостоятельно инициализируйте вновь добавленный регион.

Пример сохранения существующего указателя

Ниже приведен случай, когда новый размер положителен. Сохраните его как main.wave и запустите.

import("std::mem::alloc")::{
    mem_alloc, mem_realloc, mem_free
};

fun main() -> i32 {
    var data: ptr<u8> = mem_alloc(4);
    if (data == null) {
        return 1;
    }
    deref data[0] = 7;
    var grown: ptr<u8> = mem_realloc(data, 4, 8);
    if (grown == null) {
        mem_free(data, 4);
        return 2;
    }
    data = grown;
    println("{}", deref data[0]);
    if (mem_free(data, 8) < 0) {
        return 3;
    }

    return 0;
}

Результат выполнения:

7

Если вы перезапишете его с помощью data = mem_realloc(...) до подтверждения ошибки, вы можете потерять существующий адрес. Если перераспределение прошло успешно, старый адрес и указатели, указывающие на него, не используются.

Размер и выравнивание целевого типа

std::mem::layout
size_of<T>() -> u64
align_of<T>() -> u64

Оба значения представляют собой макет цели компиляции, а не компьютера, на котором она выполняется. size_of включает заполнение хвоста и не генерирует и не оценивает значение. При умножении количества элементов на их размер проверяем переполнение. Вы можете использовать mem_size_mul_checked и mem_size_add_checked из std::mem::ops.

mem_copy используется для копирования диапазона, который не перекрывается, а mem_move используется для копирования диапазона, который может перекрываться. Ни один из них не может определить фактическую длину выделения только по указателям, поэтому вызывающая сторона должна гарантировать границы. Списком байтов разного размера можно управлять с помощью Buffer.

Пример запроса макета

Сохраните его как main.wave и запустите. Размер и выравнивание цели, описанной в документе, i32, составляют по 4 байта каждый, поэтому выводится 4 4. Проверяйте значения других типов, особенно структур и указателей, по цели.

import("std::mem::layout")::{
    size_of, align_of
};

fun main() {
    println("{} {}", size_of<i32>(), align_of<i32>());
}

Проверьте наличие переполнения при расчете размера

Вам необходимо проверить, действителен ли count * element_size, прежде чем передавать его в функцию присваивания. Если вы выделите небольшое пространство под значение переполнения и напишете столько же, сколько исходное число, оно выйдет за пределы.

import("std::mem::ops")::{mem_size_add_checked, mem_size_mul_checked};

fun main() {
    var result: i64 = 99;

    if (mem_size_mul_checked(3, 4, &result) == 0) {
        println("bytes={}", result);
    }

    if (mem_size_add_checked(9223372036854775807, 1, &result) < 0) {
        println("overflow rejected");
    }
}

Результат выполнения:

bytes=12
overflow rejected

Результаты ошибок не записываются в размер выделения. Вы не можете обнаружить все переполнения, просто вычислив их с помощью обычной арифметики и проверив, является ли результат отрицательным. Чтобы рассчитать размер, который необходимо проверить, с самого начала используйте функцию checked.

Для перекрывающихся копий mem_move.

При перемещении части того же массива назад области ввода и вывода перекрываются. Не передавайте перекрывающиеся диапазоны в mem_copy, используйте mem_move.

import("std::mem::ops")::{mem_move};

fun main() {
    var data: array<u8, 5> = [1, 2, 3, 4, 5];

    mem_move(&data[1], &data[0], 4);

    for (var index: i32 = 0; index < 5; index += 1) {
        println("{}", data[index]);
    }
}

Результат выполнения:

1
1
2
3
4

Исходные первые четыре байта перемещаются на одну позицию вправо. Копирование их вручную может привести к считыванию уже перезаписанных значений, что приведет к случайному созданию всех единиц. API, учитывающий перекрытие, управляет направлением копирования.

Напишите функцию, которая передает право собственности

Для функций, возвращающих память, лучше всего указать адрес возврата в случае успеха и размер, необходимый для ее освобождения. Если вызывающему абоненту придется угадать размер, он может быть ошибочно освобожден. Если функция возвращает заимствованный адрес, он не должен освобождаться вызывающей стороной и описывает время жизни оригинала.

Через границы функций вы сможете отслеживать allocator → owner → deallocation. Имя или тип переменной-указателя не определяет автоматически владельца.