我的链表具有以下内存布局:
[dummy] -> (prev, ?DUMMY?, next) <-> (prev, A, next) <-> (prev, B, next)
^ ^
+---------------------------------------------------------+
虚拟 node 在插入时缓慢初始化,prev
和next
指针指向自身.插入后,prev
和next
指针将充当tail
和head
指针.
use std::marker::PhantomData;
use self::node::NodePtr;
mod node;
#[derive(Debug)]
pub struct LinkedList<T> {
dummy: Option<NodePtr<T>>,
len: usize,
_phantom: PhantomData<T>,
}
impl<T> Default for LinkedList<T> {
fn default() -> Self {
Self {
dummy: None,
len: 0,
_phantom: PhantomData,
}
}
}
impl<T> LinkedList<T> {
pub fn new() -> Self {
Default::default()
}
pub(crate) fn inner(&mut self) -> NodePtr<T> {
*self.dummy.get_or_insert(NodePtr::dummy())
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn push_front(&mut self, elem: T) {
let dummy = self.inner();
let head = dummy.next();
let new_head = NodePtr::alloc(dummy, elem, head);
head.set_prev(new_head);
dummy.set_next(new_head);
self.len += 1;
}
pub fn pop_front(&mut self) -> Option<T> {
let dummy = self.inner();
unsafe { dummy.next().dealloc() }.map(|(_, elem, new_head)| {
dummy.set_next(new_head);
new_head.set_prev(dummy);
self.len -= 1;
elem
})
}
}
impl<T> Drop for LinkedList<T> {
fn drop(&mut self) {
while self.pop_front().is_some() {}
let dummy = self.dummy.take().unwrap();
unsafe {
let _ = Box::from_raw(dummy.as_ptr());
}
}
}
node .卢比
use std::{
fmt::Debug,
mem::MaybeUninit,
ops::Not,
ptr::{self, NonNull},
};
#[derive(Debug)]
pub struct Node<T> {
prev: NodePtr<T>,
next: NodePtr<T>,
elem: MaybeUninit<T>,
}
pub struct NodePtr<T> {
ptr: NonNull<Node<T>>,
}
impl<T> Debug for NodePtr<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodePtr").field("ptr", &self.ptr).finish()
}
}
impl<T> Clone for NodePtr<T> {
fn clone(&self) -> Self {
Self { ptr: self.ptr }
}
}
impl<T> Copy for NodePtr<T> {}
impl<T> PartialEq for NodePtr<T> {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self.as_ptr(), other.as_ptr())
}
}
impl<T> Eq for NodePtr<T> {}
impl<T> NodePtr<T> {
pub unsafe fn dangling() -> Self {
Self {
ptr: NonNull::dangling(),
}
}
pub unsafe fn raw_alloc(prev: Self, elem: MaybeUninit<T>, next: Self) -> Self {
let ptr = Box::into_raw(Box::new(Node { prev, next, elem }));
let ptr = NonNull::new_unchecked(ptr);
Self { ptr }
}
pub fn alloc(prev: Self, elem: T, next: Self) -> Self {
unsafe { Self::raw_alloc(prev, MaybeUninit::new(elem), next) }
}
pub fn dummy() -> Self {
unsafe {
let dangling = Self::dangling();
let dummy = Self::raw_alloc(dangling, MaybeUninit::uninit(), dangling);
dummy.set_prev(dummy);
dummy.set_next(dummy);
dummy
}
}
pub fn prev(self) -> Self {
unsafe { (*self.as_ptr()).prev }
}
pub fn set_prev(self, ptr: Self) {
unsafe {
(*self.as_ptr()).prev = ptr;
}
}
pub fn next(self) -> Self {
unsafe { (*self.as_ptr()).next }
}
pub fn set_next(self, ptr: Self) {
unsafe {
(*self.as_ptr()).next = ptr;
}
}
pub fn is_dummy(self) -> bool {
self.prev() == self.next() && self.prev() == self
}
pub fn as_ptr(self) -> *mut Node<T> {
self.ptr.as_ptr()
}
pub unsafe fn as_ref<'a>(self) -> &'a Node<T> {
self.ptr.as_ref()
}
pub unsafe fn as_mut<'a>(mut self) -> &'a mut Node<T> {
self.ptr.as_mut()
}
pub fn get_raw(self) -> Option<NonNull<T>> {
self.is_dummy()
.not()
.then(|| unsafe { NonNull::new_unchecked((*self.as_ptr()).elem.as_mut_ptr()) })
}
pub unsafe fn get<'a>(self) -> Option<&'a T> {
self.get_raw().map(|ptr| ptr.as_ref())
}
pub unsafe fn get_mut<'a>(self) -> Option<&'a mut T> {
self.get_raw().map(|mut ptr| ptr.as_mut())
}
pub unsafe fn dealloc(self) -> Option<(Self, T, Self)> {
self.is_dummy().not().then(|| {
// println!("Deallocating...");
let Node { prev, next, elem } = *Box::from_raw(self.as_ptr());
(prev, elem.assume_init(), next)
})
}
}
使用miri运行测试用例:
#[cfg(test)]
mod test {
use super::{node::NodePtr, LinkedList};
#[test]
fn test_node() {
let dummy = NodePtr::dummy();
let node = NodePtr::alloc(dummy, 100, dummy);
dummy.set_next(node);
dummy.set_prev(node);
let (_, elem, _) = unsafe { node.dealloc() }.unwrap();
unsafe {
Box::from_raw(dummy.as_ptr());
}
assert_eq!(elem, 100);
}
#[test]
fn test_basic_front() {
let mut list = LinkedList::new();
// Try to break an empty list
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Try to break a one item list
list.push_front(10);
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
// Mess around
list.push_front(10);
assert_eq!(list.len(), 1);
list.push_front(20);
assert_eq!(list.len(), 2);
list.push_front(30);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(30));
assert_eq!(list.len(), 2);
list.push_front(40);
assert_eq!(list.len(), 3);
assert_eq!(list.pop_front(), Some(40));
assert_eq!(list.len(), 2);
assert_eq!(list.pop_front(), Some(20));
assert_eq!(list.len(), 1);
assert_eq!(list.pop_front(), Some(10));
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
assert_eq!(list.pop_front(), None);
assert_eq!(list.len(), 0);
}
}
输出目标:
The following memory was leaked: alloc86121 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1f2da8[a86121]<205762> (8 ptr bytes)╼ ╾0x1f2da8[a86121]<205762> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc86346 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1f3ef0[a86346]<206225> (8 ptr bytes)╼ ╾0x1f3ef0[a86346]<206225> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc86565 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1f4ff8[a86565]<206712> (8 ptr bytes)╼ ╾0x1f4ff8[a86565]<206712> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc86723 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1f5c58[a86723]<207063> (8 ptr bytes)╼ ╾0x1f5c58[a86723]<207063> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc86946 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1f6da8[a86946]<207524> (8 ptr bytes)╼ ╾0x1f6da8[a86946]<207524> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc87169 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1f7f60[a87169]<207985> (8 ptr bytes)╼ ╾0x1f7f60[a87169]<207985> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc87393 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1f9100[a87393]<208448> (8 ptr bytes)╼ ╾0x1f9100[a87393]<208448> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc87599 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1fa1b0[a87599]<208910> (8 ptr bytes)╼ ╾0x1fa1b0[a87599]<208910> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc87823 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1fb2f8[a87823]<209373> (8 ptr bytes)╼ ╾0x1fb2f8[a87823]<209373> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc88030 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1fc3a8[a88030]<209837> (8 ptr bytes)╼ ╾0x1fc3a8[a88030]<209837> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc88237 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1fd3a8[a88237]<210301> (8 ptr bytes)╼ ╾0x1fd3a8[a88237]<210301> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc88454 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1fe4e8[a88454]<210788> (8 ptr bytes)╼ ╾0x1fe4e8[a88454]<210788> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc88613 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1ff160[a88613]<211141> (8 ptr bytes)╼ ╾0x1ff160[a88613]<211141> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
alloc88773 (Rust heap, size: 24, align: 8) {
0x00 │ ╾0x1ffe18[a88773]<211496> (8 ptr bytes)╼ ╾0x1ffe18[a88773]<211496> (8 ptr bytes)╼ │ ╾──────╼╾──────╼
0x10 │ __ __ __ __ __ __ __ __ │ ░░░░░░░░
}
我有14个内存泄漏,但单独运行test_node
个 case 不会泄漏任何内存.通过进一步调试,我可以确保NodePtr::dealloc确实正常运行("deallocation"按预期打印了5次.请注意,此方法不会取消分配虚拟 node ,我会在Drop impl中手动取消分配虚拟 node ).代码逻辑似乎工作正常.这可能是某种微妙的未定义行为吗?