面试题答案
一键面试复杂场景描述
在实现一个自定义的内存池(Memory Pool)时,裸指针非常有用。内存池是一种预先分配一大块内存,然后在需要时从该内存块中分配小的内存片段的机制,常用于频繁分配和释放小块内存的场景,以减少内存碎片和提高内存分配效率。
安全使用裸指针解决内存管理问题
内存分配
- 初始化内存池:
首先,需要分配一大块内存作为内存池。可以使用
std::alloc::alloc
函数,该函数接受一个Layout
参数,用于描述所需内存的大小和对齐方式。
use std::alloc::{alloc, Layout};
let layout = Layout::from_size_align(1024 * 1024, 16).unwrap(); // 分配1MB内存,16字节对齐
let pool_ptr = unsafe { alloc(layout) };
- 从内存池中分配小块内存: 为了从内存池中分配小块内存,我们需要维护一个数据结构来记录内存池中的可用区域。可以使用链表来实现这一点。每个链表节点包含一个指向可用内存块的裸指针和指向下一个节点的指针。
struct FreeListNode {
next: *mut FreeListNode,
ptr: *mut u8,
}
let mut freelist_head = std::ptr::null_mut();
let block_size = 64;
let mut current_ptr = pool_ptr as *mut u8;
while current_ptr as usize < (pool_ptr as usize + layout.size()) {
let new_node = Box::into_raw(Box::new(FreeListNode {
next: freelist_head,
ptr: current_ptr,
}));
freelist_head = new_node;
current_ptr = (current_ptr as usize + block_size) as *mut u8;
}
内存释放
- 将内存块归还到内存池: 当需要释放一个内存块时,将其添加到自由链表的头部。
unsafe fn free_block(block: *mut u8, freelist_head: &mut *mut FreeListNode) {
let new_node = Box::into_raw(Box::new(FreeListNode {
next: *freelist_head,
ptr: block,
}));
*freelist_head = new_node;
}
避免悬空指针
- 使用智能指针包裹裸指针:
为了避免悬空指针,在对外提供内存块时,可以使用智能指针(如
NonNull
)来包裹裸指针。NonNull
确保指针永远不为空,并且在其析构时可以通过自定义逻辑将内存块归还到内存池。
use std::ptr::NonNull;
unsafe fn allocate_block(freelist_head: &mut *mut FreeListNode) -> Option<NonNull<u8>> {
if freelist_head.is_null() {
return None;
}
let node = *freelist_head;
*freelist_head = (*node).next;
Some(NonNull::new_unchecked((*node).ptr))
}
- 自定义 Drop 实现:
通过为包含裸指针的结构体实现
Drop
特质,在结构体析构时自动释放内存块。
struct AllocatedBlock {
ptr: NonNull<u8>,
freelist_head: *mut FreeListNode,
}
impl Drop for AllocatedBlock {
fn drop(&mut self) {
unsafe {
free_block(self.ptr.as_ptr(), &mut self.freelist_head);
}
}
}
通过上述步骤,我们可以在复杂的内存管理场景(如内存池)中安全地使用裸指针,有效处理内存的分配、释放以及避免悬空指针问题。