Можно ли удалить Box<T> как другой тип Box<S> в Rust?

В C при вызове free() вам не нужно указывать размер блока памяти. Итак, в Rust при вызове drop() по необработанному указателю можно ли передать указатель другого типа?

Вот пример: я выделяю память по типу Full, но освобождаю ее по типу Header. Это нормально?

struct Header {
    _state: i32,
}

#[repr(C)]
struct Full<T> {
    _header: Header,
    _body: T,
}

let full = Full {
    _header: Header {
        _state: 123,
    },
    _body: "hello, i'm a string",
};

let bb = Box::leak(Box::new(full));

// get ptr_t in type `*mut Full`
let ptr_t = bb as *mut Full<&'static str>;

// cast it into type `*mut Header`, to discard the generic <T>,
// then I can do something such as putting some `Full`s with different `T` in to one Vec.
// Since the `Header` is the first member in `Full`, this casting works fine.
let ptr_s = ptr_t as *mut Header;

///
// some business code to process `ptr_s` ...
///

// because we have lost the generic <T>, we can drop it as type `*mut Header` only
// Is it OK ???
unsafe { drop(Box::from_raw(ptr_s)); }

<T> в Full<T> всегда не имеет специальной Drop реализации.

Rust также нужен размер (и выравнивание), так что это не сработает.

drewtato 14.08.2024 06:10

@drewtato, что ты подразумеваешь под «не работать»? утечка памяти? не освобождает часть body в структуре Full?

wub 14.08.2024 06:42

В данном случае предпочтите Box::into_raw() вместо Box::leak().

Chayim Friedman 14.08.2024 09:42
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
3
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Удаление существует, чтобы освободить ресурсы, когда объект больше не нужен. Ресурсы здесь — это не только память, но также могут означать сокеты ОС, файловые дескрипторы и т. д. Они освобождаются путем вызова реализации признака Drop типа. Итак, если вы трансмутируете свой тип в другой и вызываете его Drop реализацию, то в целом можно получить очень непредсказуемые результаты, к которым относятся:

  • утечки памяти
  • несогласованность памяти (доступ к памяти, принадлежащей другим объектам, например, уязвимость безопасности)
  • утечка ресурсов ОС
  • двойное освобождение ресурсов ОС
  • многие другие очень непредсказуемые вещи из-за странных оптимизаций компилятора, основанных на предположении, что ваш небезопасный блок не делает ничего плохого, когда он это делает.

Итак, даже если у ваших двух типов одинаковые mem::size_of, совершенно неразумно бросать одну ленту вместо другой. И даже если оба типа не имеют специальной Drop реализации, они все равно могут иметь разное выравнивание памяти, что может привести к нарушению распределения памяти, если вы удалите один вместо другого.

Только если вы уверены, что 1) оба типа имеют одинаковый размер памяти и 2) имеют одинаковое распределение памяти и 3) не имеют специальной Drop реализации и 4) они никогда не изменятся в будущем, вы можете попробовать это сделать, но это, вероятно, не очень хорошая идея.

Возможно, вы правы, предположив, что текущий распределитель Rust по умолчанию не требует mem::size_of отбрасывать правильный объем памяти, поскольку он основан на malloc, который хранит выделенный размер каждого блока в заголовке этого блока. При этом есть еще 2 проблемы:

  1. Программа Rust может не использовать распределитель по умолчанию, поэтому вы должны иметь это в виду, если разрабатываете библиотеку, а не исполняемый файл. Произвольный распределитель может полагаться на mem::size_of при освобождении памяти.
  2. Даже при использовании распределителя по умолчанию будут удалены только поля удаляемого типа. Итак, в вашем примере поле _body не будет освобождено. Это не проблема, если T является &'static str, но если T будет String, это приведет к утечке памяти.

Спасибо. Я уверен, что: 1) Нет, они не имеют одинакового размера, но поскольку free() в C не нужен аргумент размера, я хочу знать, действительно ли размер имеет значение? 2) да, расклад тот же; 3) нет специального дропа; 4) никаких изменений в будущем.

wub 14.08.2024 08:09

Интерфейс распределителя Rust получает размер при удалении. Следовательно, реализация этого интерфейса может зависеть от этого размера.

Sebastian Redl 14.08.2024 08:22

@SebastianRedl, так что это зависит от реализации, верно? Спасибо.

wub 14.08.2024 08:37

Я обновил ответ, добавив более подробную информацию о распределителе.

Maxim Gritsenko 14.08.2024 08:38
Drop::drop вызов не является гарантией Rust (все можно забыть с помощью std::mem::forget), поэтому всегда можно не вызывать конкретный drop, это может вызвать утечку ресурсов, но это не является неправильным в смысле Rust. Т.е. реализация исходных типов Drop не имеет значения, пока целевые типы drop не вызывают двойное освобождение или тому подобное.
cafce25 14.08.2024 10:27

@cafce25 не звонить drop не является неправильным в смысле Rust, но это не значит, что все в порядке.

Jmb 14.08.2024 10:50
Ответ принят как подходящий

Короткий ответ: нет, это не нормально. Как говорится в ответе @Максима Гриценко, реализация drop может делать больше, чем просто освобождать память.

Более того, всем распределителям памяти необходимо знать размер при освобождении памяти. Библиотека C std использовалась для удобства в ущерб производительности и malloc/free отслеживала размер внутри себя. Это означает, что выделение одного char займет 16 байт памяти на 64-битных компьютерах (1 байт для самого char, 8 байтов для размера и 7 байтов заполнения, чтобы размер следующего выделения можно было правильно выровнять). , 1500% накладные расходы!).

Rust полагается на систему типов (*), чтобы отслеживать выделенный размер во время компиляции, чтобы пользователи могли иметь удобство и производительность, но для того, чтобы это работало, пользователь не должен обходить систему типов и всегда должен гарантировать, что тип количества выпавших объектов аналогична ситуации с распределением.

(*) И стандартная библиотека для динамических типов (например, Vec и подобных).

Распределителю всегда придется вести некоторый учет (например, указатели на следующее/предыдущее выделение), поэтому выделение точно такого же размера, как запрошено, маловероятно. Однако это правда, что знание размера/выравнивания позволяет более эффективно реализовать некоторые распределители (но не все).

Chayim Friedman 14.08.2024 09:46

«Распределителю всегда придется вести некоторую бухгалтерию» в целом неверно (например, распределительные распределители). Кроме того, ничто не заставляет распределитель хранить эту информацию в строке (и, таким образом, изменять размер выделения).

cafce25 14.08.2024 10:32

@ cafce25 даже распределителям удара необходимо вести некоторый учет (даже если он только увеличивает указатель при выделении), и тем более, если они хотят разрешить освобождение памяти.

Jmb 14.08.2024 10:45

Другие вопросы по теме