通过原始指针克隆类型擦除的 Arc 是否安全?

ran*_*son 5 unsafe type-erasure rust raw-pointer

我在我与包裹在一个数据工作的情况Arc,我有时最终使用into_raw,以获得原始指针的基础数据。我的用例还需要类型擦除,因此原始指针通常被强制转换为 a *const c_void,然后在重新构造Arc.

我遇到过这样一种情况,即能够在Arc不需要知道底层数据的具体类型的情况下克隆它会很有用。据我了解,只要我从未真正取消引用数据,Arc仅出于调用目的而使用虚拟类型重建 应该是安全clone的。因此,例如,这应该是安全的:

pub unsafe fn clone_raw(handle: *const c_void) -> *const c_void {
    let original = Arc::from_raw(handle);
    let copy = original.clone();
    mem::forget(original);
    Arc::into_raw(copy)
}
Run Code Online (Sandbox Code Playgroud)

有什么我遗漏的东西会使这实际上不安全吗?另外,我认为答案也适用于Rc,但如果有任何差异,请告诉我!

use*_*968 5

这几乎总是不安全的。

AnArc<T>只是一个指向堆分配结构的指针,大致看起来像

struct ArcInner<T: ?Sized> {
    strong: atomic::AtomicUsize,
    weak: atomic::AtomicUsize,
    data: T,  // You get a raw pointer to this element
}
Run Code Online (Sandbox Code Playgroud)

into_raw()给你一个指向data元素的指针。的实现Arc::from_raw()采用这样一个指针,假设data它是指向an 中元素的指针ArcInner<T>,返回内存并假设在那里找到 an ArcInner<T>。这个假设取决于 的内存布局T,特别是它的对齐方式,因此它是在 中的确切位置ArcInner

如果您调用into_raw()an Arc<U>,然后from_raw()像它是一个Arc<V>where一样进行调用,U并且对齐方式不同,则 where /所在的V偏移量计算将是错误的,并且调用将破坏数据结构。因此,不需要取消引用来触发内存不安全。UVArcInner.clone()T

实际上,这可能不是问题:由于data是两个元素之后的第三个元素usize,因此大多数元素T可能会以相同的方式对齐。但是,如果 stdlib 实现发生更改,或者您最终针对此假设错误的平台进行编译,则重建由和的内存布局不同的位置Arc<V>::from_raw创建的是不安全的并且会崩溃。Arc<U>VU


更新:

经过更多思考后,我将我的投票从“可能安全,但令人难堪”降级为“很可能不安全”,因为我总是可以这样做

#[repr(align(32))]
struct Foo;

let foo = Arc::new(Foo);
Run Code Online (Sandbox Code Playgroud)

在此示例中Foo,将对齐到 32 字节,即ArcInner<Foo>32 字节大小 (8+8+16+0),而 aArcInner<()>只是 16 字节 (8+8+0+0)。由于无法知道T类型被擦除后的对齐方式是什么,因此无法重建有效的Arc.

有一个逃生舱口在实践中可能是安全的:通过包装T到另一个Box, 的布局ArcInner<T>始终是相同的。为了强制任何用户执行此操作,您可以执行类似的操作

struct ArcBox<T>(Arc<Box<T>>)
Run Code Online (Sandbox Code Playgroud)

Deref据此实施。使用ArcBox而不是Arc强制内存布局ArcInner始终相同,因为T位于另一个指针后面。然而,这意味着所有访问都T需要双重取消引用,这可能会严重影响性能。

  • 很好的答案。我添加了绑定到“ArcInner”复制的“T: ?Sized”,因为这可以确保“data”始终位于“ArcInner”布局的末尾——没有它,或“repr(C)” `,Rust 能够将 `data` 放置在任何它想要的地方(这意味着所有的赌注都被取消了,即使对于比 `usize` 不太严格对齐的 `T` 也是如此)。 (2认同)