实际上在 Rust 中使用 alloc/dealloc 固定到堆?

问题描述 投票:0回答:1

这是我的上一个问题的后续问题。 TL;DR:我尝试通过使用

Box
堆分配自引用来创建自引用结构。有人指出,我不能依赖分配对象的地址不变(目前尚未决定)。

这导致我尝试实现一种自定义

Box
PinnedClient
,它在创建时分配给堆并在
Drop
上释放:

struct PinnedClient(pub *mut Client);

impl PinnedClient {
    unsafe fn new(client: Client) -> PinnedClient {
        // Allocate memory on the heap
        let layout = Layout::new::<Client>();
        let pointer = alloc(layout) as *mut Client;

        // Make sure it worked
        if pointer.is_null() {
            handle_alloc_error(layout);
        }

        // Move the client object to the heap
        pointer.write(client);

        // Return a `PinnedClient` object with a pointer
        // to the underlying client.
        PinnedClient(pointer)
    }
}

impl Drop for PinnedClient {
    fn drop(&mut self) {
        // Deallocate the previously allocated when
        // wrapper is dropped.
        unsafe { 
            dealloc(self.0 as *mut u8, Layout::new::<Client>());
        }
    }
}

然后我在结构中包含一个

PinnedClient
并使用原始指针创建自引用:

pub struct MyTransaction<'a> {
    transaction: Transaction<'a>,
    _client: PinnedClient
}

impl<'a> MyTransaction<'a> {
    async fn from_client<'this>(client: Client) -> Result<MyTransaction<'a>, Error> {
        let client = unsafe { PinnedClient::new(client) };
        let transaction = unsafe {
            // Convert `*mut Client` to `&mut Client`
            // This shouldn't fail since the pointer in PinnedCliend
            // is guaranteed not to be null.
            &mut *client.0
        }.transaction().await?;

        Ok(
            MyTransaction { _client: client, transaction }
        )
    }
}

现在我想知道:

  • “分发”对
    Client
  • 的可变引用是否是未定义的行为
  • 它实际上保证了内存被释放(在所有情况下都会调用
    dealloc
    吗)?

我有点焦虑,因为自我参照结构应该很难,我觉得我错过了一些东西。

rust allocation self-reference rust-pin
1个回答
0
投票

“分发”对

Client

的可变引用是否是未定义的行为

不,不是(当然,假设您保留所有别名规则并且不分发two这样的引用)。

但是,正如您之前的问题中所指出的,您需要交换字段,因为目前

Transaction
Client
之后被删除,但它可能在删除期间访问它,从而导致释放后使用。

它实际上保证了内存被释放(在所有情况下都会调用

dealloc
吗)?

是和不是。

不,因为 在 Rust 中,不保证会调用析构函数(除非

Pin
有特殊要求)。如果
MyTransaction
的析构函数未运行(由于
std::mem::forget()
或由于
Rc
循环或其他原因),则不会释放内存。

但也是肯定的,因为问题你可能想问的答案是肯定的:如果指针是

Box
,则每次内存被释放时,也将与你的智能指针一起释放。
Box
也使用
Drop
,它并不特殊(但在其他方面很特殊)。

但是...

你又错过了。您的代码仍然泄漏资源。

Client后备内存

将被释放,但
Client
本身
不会被删除。这意味着,如果您的客户端拥有一些资源(堆内存、文件描述符、网络连接等),它们将不会被释放。

即使

当前Client

没有任何滴胶,如果它来自您无法控制的库,则没有任何东西可以保证它将来不会继续没有滴胶。添加 
Drop
 impl 不被视为重大更改。所以你仍然需要放弃它,以适应未来。

幸运的是,这很容易:

impl Drop for PinnedClient { fn drop(&mut self) { // Deallocate the previously allocated when // wrapper is dropped. unsafe { self.0.drop_in_place(); dealloc(self.0 as *mut u8, Layout::new::<Client>()); } } }

因为自引用结构应该很困难

你在这里问了两个问题,你两次微妙地失败了,无法编写无 UB 代码。这对你来说还不够难吗?

© www.soinside.com 2019 - 2024. All rights reserved.