В 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
реализации.
@drewtato, что ты подразумеваешь под «не работать»? утечка памяти? не освобождает часть body
в структуре Full
?
В данном случае предпочтите Box::into_raw()
вместо Box::leak()
.
Удаление существует, чтобы освободить ресурсы, когда объект больше не нужен. Ресурсы здесь — это не только память, но также могут означать сокеты ОС, файловые дескрипторы и т. д. Они освобождаются путем вызова реализации признака Drop
типа. Итак, если вы трансмутируете свой тип в другой и вызываете его Drop
реализацию, то в целом можно получить очень непредсказуемые результаты, к которым относятся:
Итак, даже если у ваших двух типов одинаковые mem::size_of
, совершенно неразумно бросать одну ленту вместо другой. И даже если оба типа не имеют специальной Drop
реализации, они все равно могут иметь разное выравнивание памяти, что может привести к нарушению распределения памяти, если вы удалите один вместо другого.
Только если вы уверены, что 1) оба типа имеют одинаковый размер памяти и 2) имеют одинаковое распределение памяти и 3) не имеют специальной Drop
реализации и 4) они никогда не изменятся в будущем, вы можете попробовать это сделать, но это, вероятно, не очень хорошая идея.
Возможно, вы правы, предположив, что текущий распределитель Rust по умолчанию не требует mem::size_of
отбрасывать правильный объем памяти, поскольку он основан на malloc
, который хранит выделенный размер каждого блока в заголовке этого блока. При этом есть еще 2 проблемы:
mem::size_of
при освобождении памяти._body
не будет освобождено. Это не проблема, если T
является &'static str
, но если T
будет String
, это приведет к утечке памяти.Спасибо. Я уверен, что: 1) Нет, они не имеют одинакового размера, но поскольку free()
в C не нужен аргумент размера, я хочу знать, действительно ли размер имеет значение? 2) да, расклад тот же; 3) нет специального дропа; 4) никаких изменений в будущем.
Интерфейс распределителя Rust получает размер при удалении. Следовательно, реализация этого интерфейса может зависеть от этого размера.
@SebastianRedl, так что это зависит от реализации, верно? Спасибо.
Я обновил ответ, добавив более подробную информацию о распределителе.
Drop::drop
вызов не является гарантией Rust (все можно забыть с помощью std::mem::forget
), поэтому всегда можно не вызывать конкретный drop
, это может вызвать утечку ресурсов, но это не является неправильным в смысле Rust. Т.е. реализация исходных типов Drop
не имеет значения, пока целевые типы drop
не вызывают двойное освобождение или тому подобное.
@cafce25 не звонить drop
не является неправильным в смысле Rust, но это не значит, что все в порядке.
Короткий ответ: нет, это не нормально. Как говорится в ответе @Максима Гриценко, реализация drop
может делать больше, чем просто освобождать память.
Более того, всем распределителям памяти необходимо знать размер при освобождении памяти. Библиотека C std использовалась для удобства в ущерб производительности и malloc
/free
отслеживала размер внутри себя. Это означает, что выделение одного char
займет 16 байт памяти на 64-битных компьютерах (1 байт для самого char
, 8 байтов для размера и 7 байтов заполнения, чтобы размер следующего выделения можно было правильно выровнять). , 1500% накладные расходы!).
Rust полагается на систему типов (*), чтобы отслеживать выделенный размер во время компиляции, чтобы пользователи могли иметь удобство и производительность, но для того, чтобы это работало, пользователь не должен обходить систему типов и всегда должен гарантировать, что тип количества выпавших объектов аналогична ситуации с распределением.
(*) И стандартная библиотека для динамических типов (например, Vec
и подобных).
Распределителю всегда придется вести некоторый учет (например, указатели на следующее/предыдущее выделение), поэтому выделение точно такого же размера, как запрошено, маловероятно. Однако это правда, что знание размера/выравнивания позволяет более эффективно реализовать некоторые распределители (но не все).
«Распределителю всегда придется вести некоторую бухгалтерию» в целом неверно (например, распределительные распределители). Кроме того, ничто не заставляет распределитель хранить эту информацию в строке (и, таким образом, изменять размер выделения).
@ cafce25 даже распределителям удара необходимо вести некоторый учет (даже если он только увеличивает указатель при выделении), и тем более, если они хотят разрешить освобождение памяти.
Rust также нужен размер (и выравнивание), так что это не сработает.