rust - 通过原始指针克隆类型擦除的 Arc 是否安全?
问题描述
我正在处理包装在 中的数据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)
}
有什么我遗漏的东西会使这实际上不安全吗?另外,我认为答案也适用Rc
,但如果有任何差异,请告诉我!
解决方案
这几乎总是不安全的。
AnArc<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()
采用这样一个指针,假设它是指向data
an 中的 -element的指针ArcInner<T>
,返回内存并假设在那里找到 an ArcInner<T>
。这个假设取决于 的内存布局T
,特别是它的对齐方式,因此它在ArcInner
.
如果您调用into_raw()
anArc<U>
然后调用from_raw()
,就好像它是Arc<V>
whereU
并且对齐方式不同,则 where / is inV
的偏移量计算将是错误的,并且调用 to将破坏数据结构。因此不需要取消引用来触发内存不安全。U
V
ArcInner
.clone()
T
在实践中,这可能不是问题:由于data
是两个元素之后的第三个usize
元素,因此大多数元素T
可能会以相同的方式对齐。但是,如果 stdlib 实现发生变化,或者您最终为这个假设错误的平台进行编译,那么重建由内存布局不同Arc<V>::from_raw
的地方创建的平台将不安全并崩溃。Arc<U>
V
U
更新:
再想一想,我将我的投票从“可能是安全的,但令人毛骨悚然”降级为“很可能不安全”,因为我总能做到
#[repr(align(32))]
struct Foo;
let foo = Arc::new(Foo);
在此示例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>>)
并Deref
在此实施。使用ArcBox
而不是Arc
强制内存布局ArcInner
始终相同,因为T
在另一个指针后面。然而,这意味着所有访问都T
需要双重取消引用,这可能会严重影响性能。
推荐阅读
- c# - C# POS - 'Microsoft.PointOfService.Management.Explorer' 的类型初始化程序引发异常
- azure - Azure 映射 REST API 链接不起作用
- php - 可能有错误的 PHP 安装 - 信息页空白 - LAMP
- c++ - 如何通过 Valgrind 查找 QThreads 程序中的内存泄漏在哪里?
- lora - 路由键逻辑
- android - Kotlin 设置文本方法不起作用
- python - 如何在 git 中测试前/后提交脚本?
- performance - 从 Haskell 中的一元流中提取更多性能
- angular - 从其他模块导入组件时出错
- java - 在android中扭曲视图