通过原始指针克隆类型擦除的 Arc 是否安全?
Is it safe to clone a type-erased Arc via raw pointer?
我正在处理包裹在 Arc
, and I sometimes end up using into_raw
中的数据,以获取指向基础数据的原始指针。我的用例还要求类型擦除,因此原始指针经常被转换为 *const c_void
,然后在重新构造 Arc
.
时转换回适当的具体类型
我已经 运行 遇到了这样一种情况,即能够克隆 Arc
而无需知道基础数据的具体类型。据我了解,仅出于调用 clone
的目的使用虚拟类型重建 Arc
应该是安全的,只要我从未真正取消引用数据。因此,例如,这应该是安全的:
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)
}
有什么我遗漏的东西会让这实际上不安全吗?另外,我假设答案也适用于 Rc
,但如果有任何差异,请告诉我!
这几乎总是不安全的。
Arc<T>
只是指向堆分配结构的指针,大致看起来像
struct ArcInner<T: ?Sized> {
strong: atomic::AtomicUsize,
weak: atomic::AtomicUsize,
data: T, // You get a raw pointer to this element
}
into_raw()
为您提供指向 data
元素的指针。 Arc::from_raw()
的实现采用这样一个指针, 假定 它是指向 ArcInner<T>
中的 data
元素的指针,返回内存并假定 在那里找到 ArcInner<T>
。这个假设取决于 T
的内存布局,特别是它的对齐方式,因此它在 ArcInner
.
中的确切位置
如果您在 Arc<U>
上调用 into_raw()
,然后调用 from_raw()
就好像它是 Arc<V>
,其中 U
和 V
对齐方式不同,U
/V
在 ArcInner
中的偏移计算将是错误的,对 .clone()
的调用将破坏数据结构。 因此不需要取消引用 T
来触发内存不安全。
在实践中,这可能不是问题:因为 data
是两个 usize
元素之后的第三个元素,大多数 T
可能会以相同的方式对齐。但是,如果 stdlib-implementation 发生变化,或者您最终为这个假设错误的平台编译,则重建由 Arc<U>
创建的 Arc<V>::from_raw
,其中 V
的内存布局和U
不同会不安全和崩溃。
更新:
再考虑一下后,我将我的投票从 "might be safe, but cringy" 降级为 "most likely unsafe",因为我总能做到
#[repr(align(32))]
struct Foo;
let foo = Arc::new(Foo);
在此示例中,Foo
将对齐到 32 个字节,使 ArcInner<Foo>
大小为 32 个字节 (8+8+16+0),而 ArcInner<()>
仅为 16 个字节(8+8+0+0)。由于在删除类型后无法判断 T
的对齐方式,因此无法重建有效的 Arc
.
有一个在实践中可能是安全的逃生舱口:通过将 T
包装到另一个 Box
中,ArcInner<T>
的布局始终相同。为了对任何用户强制执行此操作,您可以执行类似
struct ArcBox<T>(Arc<Box<T>>)
并在其上实施 Deref
。使用 ArcBox
而不是 Arc
强制 ArcInner
的内存布局始终相同,因为 T
在另一个指针后面。然而,这意味着对 T
的所有访问都需要双重取消引用,这可能会严重影响性能。
我正在处理包裹在 Arc
, and I sometimes end up using into_raw
中的数据,以获取指向基础数据的原始指针。我的用例还要求类型擦除,因此原始指针经常被转换为 *const c_void
,然后在重新构造 Arc
.
我已经 运行 遇到了这样一种情况,即能够克隆 Arc
而无需知道基础数据的具体类型。据我了解,仅出于调用 clone
的目的使用虚拟类型重建 Arc
应该是安全的,只要我从未真正取消引用数据。因此,例如,这应该是安全的:
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)
}
有什么我遗漏的东西会让这实际上不安全吗?另外,我假设答案也适用于 Rc
,但如果有任何差异,请告诉我!
这几乎总是不安全的。
Arc<T>
只是指向堆分配结构的指针,大致看起来像
struct ArcInner<T: ?Sized> {
strong: atomic::AtomicUsize,
weak: atomic::AtomicUsize,
data: T, // You get a raw pointer to this element
}
into_raw()
为您提供指向 data
元素的指针。 Arc::from_raw()
的实现采用这样一个指针, 假定 它是指向 ArcInner<T>
中的 data
元素的指针,返回内存并假定 在那里找到 ArcInner<T>
。这个假设取决于 T
的内存布局,特别是它的对齐方式,因此它在 ArcInner
.
如果您在 Arc<U>
上调用 into_raw()
,然后调用 from_raw()
就好像它是 Arc<V>
,其中 U
和 V
对齐方式不同,U
/V
在 ArcInner
中的偏移计算将是错误的,对 .clone()
的调用将破坏数据结构。 因此不需要取消引用 T
来触发内存不安全。
在实践中,这可能不是问题:因为 data
是两个 usize
元素之后的第三个元素,大多数 T
可能会以相同的方式对齐。但是,如果 stdlib-implementation 发生变化,或者您最终为这个假设错误的平台编译,则重建由 Arc<U>
创建的 Arc<V>::from_raw
,其中 V
的内存布局和U
不同会不安全和崩溃。
更新:
再考虑一下后,我将我的投票从 "might be safe, but cringy" 降级为 "most likely unsafe",因为我总能做到
#[repr(align(32))]
struct Foo;
let foo = Arc::new(Foo);
在此示例中,Foo
将对齐到 32 个字节,使 ArcInner<Foo>
大小为 32 个字节 (8+8+16+0),而 ArcInner<()>
仅为 16 个字节(8+8+0+0)。由于在删除类型后无法判断 T
的对齐方式,因此无法重建有效的 Arc
.
有一个在实践中可能是安全的逃生舱口:通过将 T
包装到另一个 Box
中,ArcInner<T>
的布局始终相同。为了对任何用户强制执行此操作,您可以执行类似
struct ArcBox<T>(Arc<Box<T>>)
并在其上实施 Deref
。使用 ArcBox
而不是 Arc
强制 ArcInner
的内存布局始终相同,因为 T
在另一个指针后面。然而,这意味着对 T
的所有访问都需要双重取消引用,这可能会严重影响性能。