Trait std::alloc::GlobalAlloc

1.28.0 · source ·
pub unsafe trait GlobalAlloc {
    // Required methods
    unsafe fn alloc(&self, layout: Layout) -> *mut u8;
    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout);

    // Provided methods
    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { ... }
    unsafe fn realloc(
        &self,
        ptr: *mut u8,
        layout: Layout,
        new_size: usize
    ) -> *mut u8 { ... }
}
Expand description

可以通过 #[global_allocator] 属性将其分配为标准库的默认内存分配器。

某些方法要求通过分配器 currently 分配存储块。这意味着:

  • 该存储块的起始地址先前是由先前的调用返回到诸如 alloc 的分配方法的,并且

  • 内存块尚未随后被释放,而是通过传递给诸如 dealloc 的释放方法或传递给返回非空指针的重新分配方法来对块进行释放。

Example

use std::alloc::{GlobalAlloc, Layout};
use std::cell::UnsafeCell;
use std::ptr::null_mut;
use std::sync::atomic::{
    AtomicUsize,
    Ordering::{Acquire, SeqCst},
};

const ARENA_SIZE: usize = 128 * 1024;
const MAX_SUPPORTED_ALIGN: usize = 4096;
#[repr(C, align(4096))] // 4096 == MAX_SUPPORTED_ALIGN
struct SimpleAllocator {
    arena: UnsafeCell<[u8; ARENA_SIZE]>,
    remaining: AtomicUsize, // 我们从顶部分配,倒计时
}

#[global_allocator]
static ALLOCATOR: SimpleAllocator = SimpleAllocator {
    arena: UnsafeCell::new([0x55; ARENA_SIZE]),
    remaining: AtomicUsize::new(ARENA_SIZE),
};

unsafe impl Sync for SimpleAllocator {}

unsafe impl GlobalAlloc for SimpleAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let size = layout.size();
        let align = layout.align();

        // `Layout` 契约禁止使用 align=0 或 align not power of 制作 `Layout` 2.
        // 所以我们可以放心地使用掩码来确保对齐,而不必担心 UB。
        let align_mask_to_round_down = !(align - 1);

        if align > MAX_SUPPORTED_ALIGN {
            return null_mut();
        }

        let mut allocated = 0;
        if self
            .remaining
            .fetch_update(SeqCst, SeqCst, |mut remaining| {
                if size > remaining {
                    return None;
                }
                remaining -= size;
                remaining &= align_mask_to_round_down;
                allocated = remaining;
                Some(remaining)
            })
            .is_err()
        {
            return null_mut();
        };
        self.arena.get().cast::<u8>().add(allocated)
    }
    unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
}

fn main() {
    let _s = format!("allocating a string!");
    let currently = ALLOCATOR.remaining.load(Acquire);
    println!("allocated so far: {}", ARENA_SIZE - currently);
}
Run

Safety

由于多种原因,GlobalAlloc trait 是 unsafe trait,实现者必须确保遵守以下契约:

  • 如果分配器解散,这是未定义的行为。可以在 future 中取消此限制,但是当前来自任何这些函数的 panic 都可能导致内存不安全。

  • Layout 查询和计算一般情况下必须是正确的。允许 trait 的调用者依赖于每种方法上定义的协定,实现者必须确保此类协定保持正确。

  • 您不能依赖实际发生的分配,即使源中有显式的堆分配。 优化器可能会检测到未使用的分配,该分配器可以将其完全消除或移到栈,因此从不调用分配器。 优化器可能进一步假设分配是无误的,因此由于分配器故障而导致分配器失败的代码现在可能突然起作用,因为优化器解决了分配需求。 更具体地说,无论您的自定义分配器是否允许计算发生了多少分配,下面的代码示例都是不正确的。

    drop(Box::new(42));
    let number_of_heap_allocs = /* call private allocator API */;
    unsafe { std::intrinsics::assume(number_of_heap_allocs > 0); }
    Run

    请注意,上面提到的优化并不是唯一可以应用的优化。如果可以在不更改程序行为的情况下将其删除,则通常可能不依赖于发生的堆分配。 分配的发生与否不是程序行为的一部分,即使可以通过分配器检测到分配,该分配器通过打印或其他方式跟踪分配也会产生副作用。

Required Methods§

source

unsafe fn alloc(&self, layout: Layout) -> *mut u8

按照给定的 layout 分配内存。

返回指向新分配的内存的指针,或者返回 null 以指示分配失败。

Safety

该函数是不安全的,因为如果调用者不确保 layout 的大小为非零,则可能导致未定义的行为。

(扩展子特性可能提供行为的更具体限制,例如,保证响应零大小分配请求的前哨地址或空指针。)

分配的内存块可能会初始化也可能不会初始化。

Errors

返回空指针表示内存已耗尽,或者 layout 不满足此分配器的大小或对齐约束。

鼓励实现在内存耗尽时返回 null 而不是中止,但这不是严格的要求。 (具体来说:在一个底层的原生分配库上实现此 trait 是 合法的,该本地分配库在内存耗尽时中止。)

鼓励希望响应分配错误而中止计算的客户端调用 handle_alloc_error 函数,而不是直接调用 panic! 或类似方法。

source

unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout)

使用给定的 layout 在给定的 ptr 指针处释放内存块。

Safety

该函数是不安全的,因为如果调用者不能确保满足以下所有条件,则可能导致未定义的行为:

  • ptr 必须表示当前通过此分配器分配的内存块,

  • layout 必须与用于分配该内存块的布局相同。

Provided Methods§

source

unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8

行为类似于 alloc,但也确保在返回之前将内容设置为零。

Safety

出于与 alloc 相同的原因,此函数是不安全的。 但是,保证已分配的内存块将被初始化。

Errors

alloc 一样,返回空指针表示内存已耗尽或 layout 不满足分配器的大小或对齐约束。

鼓励希望响应分配错误而中止计算的客户端调用 handle_alloc_error 函数,而不是直接调用 panic! 或类似方法。

source

unsafe fn realloc( &self, ptr: *mut u8, layout: Layout, new_size: usize ) -> *mut u8

将内存块缩小或增大到给定的 new_size (以字节为单位)。 该块由给定的 ptr 指针和 layout 描述。

如果返回非空指针,则 ptr 引用的内存块的所有权已转移到此分配器。 对旧 ptr 的任何访问都是未定义行为,即使分配保持原样。新返回的指针是现在访问这块内存的唯一有效指针。

新内存块分配了 layout,但 size 更新为 new_size (以字节为单位)。 当使用 dealloc 释放新内存块时,必须使用这个新布局。 确保新存储块的范围 0..min(layout.size(), new_size) 与原始存储块具有相同的值。

如果此方法返回 null,则该存储块的所有权尚未转移到此分配器,并且该存储块的内容不会更改。

Safety

该函数是不安全的,因为如果调用者不能确保满足以下所有条件,则可能导致未定义的行为:

  • ptr 当前必须通过这个分配器分配,

  • layout 必须与用于分配该内存块的布局相同,

  • new_size 必须大于零。

  • new_size 在四舍五入到最接近的 layout.align() 倍数时不得溢出 isize (即,四舍五入的值必须小于或等于 isize::MAX)。

(扩展子特性可能提供行为的更具体限制,例如,保证响应零大小分配请求的前哨地址或空指针。)

Errors

如果新布局不符合分配器的大小和对齐约束,或者重新分配失败,则返回 null。

鼓励实现在内存耗尽时返回 null 而不是 panic 或中止,但是这不是严格的要求。 (具体来说:在一个底层的原生分配库上实现此 trait 是 合法的,该本地分配库在内存耗尽时中止。)

鼓励希望响应重新分配错误而中止计算的客户调用 handle_alloc_error 函数,而不是直接调用 panic! 或类似方法。

Implementors§