为什么 Rust 中的嵌套分配器会导致堆损坏?

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

我一直在尝试 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) };
        }
    }
}

为什么会出现此错误以及如何解决该错误?

rust memory memory-management allocation allocator
1个回答
0
投票

问题不在于分层分配器,通过简单地

clone
分配器,你会导致
drop
中的双重释放。

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