我一直在尝试 Rust allocator_api 功能并开发了一个简单的线性分配器。我的测试用例表明这按预期工作。但是,我希望能够嵌套分配器,例如:
let linear1 = LinearAllocator::with_capacity(32);
let linear2 = LinearAllocator::with_capacity_in(4, linear1.clone());
assert!(linear1.allocate(Layout::new::<u32>()).is_ok());
assert!(linear2.allocate(Layout::new::<u32>()).is_ok());
但是当我这样做时,我的测试失败并显示以下输出:
error: test failed, to rerun pass `-p allocators --bin allocators`
Caused by:
process didn't exit successfully: `D:\Dev\allocators\target\debug\deps\allocators-a76735fe34fe79e2.exe test::linear_alloc_nested_with_linear_alloc --exact --nocapture` (exit code: 0xc0000374, STATUS_HEAP_CORRUPTION)
我可以从
dbg!(&self)
中的 Allocator::deallocate()
看到它正在尝试从 Global
分配器而不是内部 LinearAllocator
取消分配:
[src\main.rs:126:9] &self = LinearAllocator {
size: 32,
allocated: 4,
data: 0x00000158a8cd6400,
layout: Layout {
size: 32,
align: 1 (1 << 0),
},
alloc: Global,
}
也许这是预料之中的,因为 IIRC Rust 按声明顺序下降(此处为
linear1
,然后为 linear2
)。但是,如果我手动按声明的相反顺序删除,则会出现相同的错误。所以我不能 100% 确定我走在正确的道路上。
我确实用
deallocate
打电话给 Drop
。如果我调试测试,它也会在此处显示问题的原因。但我不明白为什么。我需要能够解除分配,例如如果我将一个线性分配器嵌套在另一个解除分配的分配器中。如果我在 drop 中排除对 deallocate
的调用,则测试通过。如果我在 Drop
中什么都不做,正确的事情会发生吗?
这是完整的
LinearAllocator
代码:
#[derive(Clone, Debug)]
pub struct LinearAllocator<A: Allocator = Global> {
size: usize,
allocated: Arc<AtomicUsize>,
data: *mut u8,
layout: Layout,
alloc: A,
}
impl Default for LinearAllocator<Global> {
fn default() -> Self {
Self {
size: 0,
allocated: Arc::new(AtomicUsize::new(0)),
data: std::ptr::null_mut(),
layout: Layout::new::<u8>(),
alloc: Global,
}
}
}
impl LinearAllocator<Global> {
pub fn with_capacity(size: usize) -> Self {
Self::with_capacity_in(size, Global)
}
}
impl<A: Allocator> LinearAllocator<A> {
pub fn with_capacity_in(size: usize, alloc: A) -> Self {
let layout = Layout::array::<u8>(size).unwrap();
let data = unsafe { alloc.allocate(layout).unwrap().as_mut() as *mut [u8] as *mut u8 };
Self {
size,
allocated: Arc::new(AtomicUsize::new(0)),
data,
layout,
alloc,
}
}
pub fn reset(&self) {
self.allocated.store(0, Ordering::Relaxed);
}
}
unsafe impl<A: Allocator + Debug> Allocator for LinearAllocator<A> {
fn allocate(&self, layout: Layout) -> Result<std::ptr::NonNull<[u8]>, AllocError> {
let size = layout.size();
let align = layout.align();
let remainder = size % align;
let aligned_size = if remainder == 0 {
size
} else {
size + align - remainder
};
let mut current = self.allocated.load(Ordering::Relaxed);
loop {
if self.size - current < aligned_size {
return Err(AllocError);
}
let new = current + aligned_size;
if let Err(actual) =
self.allocated
.compare_exchange(current, new, Ordering::Relaxed, Ordering::Relaxed)
{
current = actual;
} else {
break;
}
}
// Safety: We previously checked that there is enough space in data.
let data = unsafe { self.data.add(current) };
let ptr = std::ptr::slice_from_raw_parts_mut(data, aligned_size);
std::ptr::NonNull::new(ptr).ok_or(AllocError)
}
unsafe fn deallocate(&self, _ptr: std::ptr::NonNull<u8>, _layout: Layout) {
// Linear allocator does not deallocate.
dbg!(&self);
}
}
impl<A: Allocator> Drop for LinearAllocator<A> {
fn drop(&mut self) {
self.reset();
if let Some(ptr) = std::ptr::NonNull::new(self.data) {
unsafe { A::deallocate(&self.alloc, ptr, self.layout) };
}
}
}
为什么会出现此错误以及如何解决该错误?
问题不在于分层分配器,通过简单地
clone
分配器,你会导致drop
中的双重释放。