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);
}
RunSafety
由于多种原因,GlobalAlloc
trait 是 unsafe
trait,实现者必须确保遵守以下契约:
-
如果分配器解散,这是未定义的行为。可以在 future 中取消此限制,但是当前来自任何这些函数的 panic 都可能导致内存不安全。
-
Layout
查询和计算一般情况下必须是正确的。允许 trait 的调用者依赖于每种方法上定义的协定,实现者必须确保此类协定保持正确。 -
您不能依赖实际发生的分配,即使源中有显式的堆分配。 优化器可能会检测到未使用的分配,该分配器可以将其完全消除或移到栈,因此从不调用分配器。 优化器可能进一步假设分配是无误的,因此由于分配器故障而导致分配器失败的代码现在可能突然起作用,因为优化器解决了分配需求。 更具体地说,无论您的自定义分配器是否允许计算发生了多少分配,下面的代码示例都是不正确的。
ⓘ
Rundrop(Box::new(42)); let number_of_heap_allocs = /* call private allocator API */; unsafe { std::intrinsics::assume(number_of_heap_allocs > 0); }
请注意,上面提到的优化并不是唯一可以应用的优化。如果可以在不更改程序行为的情况下将其删除,则通常可能不依赖于发生的堆分配。 分配的发生与否不是程序行为的一部分,即使可以通过分配器检测到分配,该分配器通过打印或其他方式跟踪分配也会产生副作用。
Required Methods§
sourceunsafe fn alloc(&self, layout: Layout) -> *mut u8
unsafe fn alloc(&self, layout: Layout) -> *mut u8
按照给定的 layout
分配内存。
返回指向新分配的内存的指针,或者返回 null 以指示分配失败。
Safety
该函数是不安全的,因为如果调用者不确保 layout
的大小为非零,则可能导致未定义的行为。
(扩展子特性可能提供行为的更具体限制,例如,保证响应零大小分配请求的前哨地址或空指针。)
分配的内存块可能会初始化也可能不会初始化。
Errors
返回空指针表示内存已耗尽,或者 layout
不满足此分配器的大小或对齐约束。
鼓励实现在内存耗尽时返回 null 而不是中止,但这不是严格的要求。 (具体来说:在一个底层的原生分配库上实现此 trait 是 合法的,该本地分配库在内存耗尽时中止。)
鼓励希望响应分配错误而中止计算的客户端调用 handle_alloc_error
函数,而不是直接调用 panic!
或类似方法。
Provided Methods§
sourceunsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8
行为类似于 alloc
,但也确保在返回之前将内容设置为零。
Safety
出于与 alloc
相同的原因,此函数是不安全的。
但是,保证已分配的内存块将被初始化。
Errors
像 alloc
一样,返回空指针表示内存已耗尽或 layout
不满足分配器的大小或对齐约束。
鼓励希望响应分配错误而中止计算的客户端调用 handle_alloc_error
函数,而不是直接调用 panic!
或类似方法。
sourceunsafe fn realloc(
&self,
ptr: *mut u8,
layout: Layout,
new_size: usize
) -> *mut u8
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!
或类似方法。