英文:
Why `std::mem::forget` cannot be used for creating static references?
问题
在上面的代码片段中,为什么会发生段错误?std::mem::forget
只是接管所有权并不运行析构函数/ drop 函数,这可以用来创建生命周期为静态的引用,这个逻辑有什么问题吗?我是 Rust 的新手,希望了解引用、结构体 T 的实例和指针之间的关系。从一个天真的想法来看,上面的代码逻辑上似乎是正确的。请解释并纠正。
解释:
在Rust中,std::mem::forget
确实是用来接管所有权并不运行析构函数的函数。但是,要正确使用它,你需要确保在忘记值之前,没有任何代码试图访问已经被忘记的值。否则,会出现悬垂引用或者访问已释放的内存的情况,这会导致不可预测的行为,包括段错误。
在你的代码中,forget
函数被用在了forget_me_as_ptr
和forget_me_as_mut_ref
方法中,这些方法返回了对结构体Self
的引用,但在这些引用返回之前,forget
已经被调用,导致了悬垂引用或者访问已释放内存的问题。
具体来说,在以下代码中:
fn forget_me_as_ptr(self) -> *const Self {
let p = addr_of!(self); // 获取指向 self 的指针
forget(self); // 接管了 self 的所有权,但没有运行析构函数
p // 返回指向 self 的指针
}
fn forget_me_as_mut_ref(mut self) -> &'static mut Self {
let p = addr_of_mut!(self); // 获取指向 self 的可变指针
forget(self); // 接管了 self 的所有权,但没有运行析构函数
unsafe { p.as_mut().unwrap() } // 解引用可变指针,这里会导致悬垂引用或访问已释放内存
}
在调用forget
之后,self
的所有权被接管,但是在forget
之后仍然尝试访问已经被释放的self
,这会导致段错误。
要解决这个问题,你应该确保在调用forget
之后不再尝试访问被忘记的值,或者重新设计代码以避免使用forget
,因为它通常不是创建生命周期为静态的引用的正确方式。在Rust中,通常使用生命周期参数来明确指定引用的生命周期,而不是尝试通过forget
来创建静态引用。
英文:
pub trait Forget {
fn forget_me_as_ptr(self) -> *const Self
where
Self: Sized,
{
let p = addr_of!(self);
forget(self);
p
}
fn forget_me_as_mut_ptr(self) -> *mut Self
where
Self: Sized,
{
self.forget_me_as_ptr() as *mut Self
}
fn forget_me_as_ref(self) -> &'static Self
where
Self: Sized,
{
self.forget_me_as_mut_ref()
}
fn forget_me_as_mut_ref(mut self) -> &'static mut Self
where
Self: Sized,
{
let p = addr_of_mut!(self);
forget(self);
unsafe { p.as_mut().unwrap() }
}
}
pub fn default<T: Forget + Default>() -> &'static T {
T::default().forget_me_as_ref()
}
#[derive(Debug)]
struct Player {
health: u8,
name: String,
aliases: Vec<String>,
}
impl Drop for Player {
fn drop(&mut self) {
println!("Dropping [{}, {self:p}]", type_name::<Self>());
}
}
impl Display for Player {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[{}, {self:p}] = name: {}, health: {}, aliases: {:#?}",
type_name::<Self>(),
self.name,
self.health,
self.aliases
)
}
}
impl Forget for Player {}
impl Default for Player {
fn default() -> Self {
Self {
health: 100,
name: "default".into(),
aliases: vec!["default".to_string()],
}
}
}
fn main() {
let p = default::<Player>();
println!("{p}");
}
output:
$ cargo r
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/static_factory`
Segmentation fault (core dumped)
In the above code snippet why is it segfaulting ?
std::mem::forget
just takes ownership and does not run the destructor/drop which can exactly be used to create references whose lifetime is static ? Where is the flaw in this logic ? Am new to rust and kind of wanted to understand what is the relation between references, instances of struct T, pointers. The code above looks logically correct from a naive thought. Please explain and correct.
答案1
得分: 6
使用 std::mem::forget
可以避免运行 Drop
实现,但由于 self
是一个局部变量,它的内存仍然会被回收并用于其他用途。
要获得所需的行为,您需要将其移动到一个稳定的地址,不会被重用。您可以通过将其放入一个 Box
中,获取指向它的指针,然后忘记这个盒子。幸运的是,已经有一个函数可以实现这个目的:Box::leak
。类似这样:
pub trait Forget {
// ... 其余部分 ...
fn forget_me_as_mut_ref(mut self) -> &'static mut Self
where
Self: Sized,
{
Box::leak(Box::new(self))
}
}
英文:
Using std::mem::forget
avoids running the Drop
implementation but since self
is a local variable, the memory for it will still be reclaimed and used for other things.
To get the desired behavior, you would need to move it to a stable address that won't be reused. You can do this by putting it in a Box
, getting the pointer to it, and forgetting the box. Fortunately there is already a function for that: Box::leak
. Something like:
pub trait Forget {
// ... the rest ...
fn forget_me_as_mut_ref(mut self) -> &'static mut Self
where
Self: Sized,
{
Box::leak(Box::new(self))
}
答案2
得分: 3
局部变量,就像函数的参数一样,存储在堆栈上,无论是否调用drop
函数,都不会有任何影响,因为一旦离开函数,对它的任何指针都会变成悬空指针。因此,当p
指向局部内存(如参数self
)并返回结果时,unsafe { p.as_mut().unwrap() }
是不安全的。
英文:
Local variables, like the parameters of a function live on the stack, whether drop
is called on it's memory doesn't make a difference in that any pointer to it becomes dangling as soon as you leave the function. Therefore unsafe { p.as_mut().unwrap() }
is unsound when p
points to local memory (such as the argument self
) and you return the result.
答案3
得分: 0
我的嫌疑对象涉及以下代码行:
pub fn default<T: Forget + Default>() -> &'static T {
T::default().forget_me_as_ref()
}
T::default()
在堆栈上创建了一个临时对象,为该对象创建了一个引用。一旦函数调用结束,与堆栈分配的对象对应的内存可能会被破坏。如果对象是在堆上创建的,我认为这个问题会消失。
英文:
My suspect is with the line:
pub fn default<T: Forget + Default>() -> &'static T {
T::default().forget_me_as_ref()
}
T::default()
creates a temporary object on stack for which we create a reference. Once the function call ends the memory corresponding to the stack allocated object is lets say garbled. Had the object been created on heap i think this would go away.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论