0

在实现链表时,我正在努力学习原始指针。一段简单的代码给了我意想不到的结果,我很难找到任何解释:

use std::cmp::PartialEq;
use std::default::Default;
use std::ptr;

pub struct LinkedListElement<T> {
    pub data: T,
    pub next: *mut LinkedListElement<T>,
}

pub struct LinkedList<T> {
    head: *mut LinkedListElement<T>,
}

impl<T: PartialEq> LinkedListElement<T> {
    pub fn new(elem: T, next: Option<*mut LinkedListElement<T>>) -> LinkedListElement<T> {
        let mut_ptr = match next {
            Some(t) => t,
            None => ptr::null_mut(),
        };
        let new_elem = LinkedListElement {
            data: elem,
            next: mut_ptr,
        };
        if !mut_ptr.is_null() {
            println!(
                "post create ll mut ptr: {:p}, post create ll mut ptr next {:p}",
                mut_ptr,
                unsafe { (*mut_ptr).next }
            );
        }
        new_elem
    }
}

impl<T: PartialEq + Default> LinkedList<T> {
    pub fn new(elem: T) -> LinkedList<T> {
        LinkedList {
            head: &mut LinkedListElement::new(elem, None),
        }
    }

    pub fn insert(&mut self, elem: T) {
        println!("head: {:p} . next: {:p}", self.head, unsafe {
            (*self.head).next
        });
        let next = Some(self.head);
        let mut ll_elem = LinkedListElement::new(elem, next);
        println!(
            "before pointer head: {:p}. before pointer next {:p}",
            self.head,
            unsafe { (*self.head).next }
        );
        let ll_elem_ptr = &mut ll_elem as *mut LinkedListElement<T>;
        self.head = ll_elem_ptr;
    }
}

fn main() {
    let elem: i32 = 32;
    let second_elem: i32 = 64;
    let third_elem: i32 = 72;
    let mut list = LinkedList::new(elem);
    list.insert(second_elem);
    list.insert(third_elem);
}

操场

这段代码给了我以下输出:

head: 0x7ffe163275e8 . next: 0x0
post create ll mut ptr: 0x7ffe163275e8, post create ll mut ptr next 0x0
before pointer head: 0x7ffe163275e8. before pointer next 0x0
head: 0x7ffe16327560 . next: 0x7ffe163275e8
post create ll mut ptr: 0x7ffe16327560, post create ll mut ptr next 0x7ffe163275e8
before pointer head: 0x7ffe16327560. before pointer next 0x7ffe16327560

对于前 2 个元素,代码的行为符合预期:它创建了一个元素,其下一个元素为空指针。这是添加第二个元素后的状态:

{
  head: {
    elem: 64,
    next: {
      elem: 32,
      next: nullptr
    }
  }
}

64 -> 32 -> null

当添加第三个元素时,事情变得很奇怪,链表变成了这样的东西:

{
  head: {
    elem: 72,
    next: {
      elem: 72,
      next: {
        elem: 72,
        next: ...
      }
    }
  }
}

72 -> 72 -> 72 -> ...

似乎链表元素的next字段开始指向元素本身。

我已经调试了该LinkedListElement::new方法,发现应该从中返回正确的元素:

{
  elem: 72,
  next: {
    elem: 64,
    next: {
      elem: 32,
      next: nullptr
    }
  }
}

由于某种原因,在它返回到LinkedList::insert方法之后,甚至在self.head重新分配之前,它的内容都会LinkedList self“损坏”。

我知道在 Rust 中使用原始指针并不习惯,但我仍然想学习它们。

4

1 回答 1

5

恭喜,你已经成功地证明了为什么 Rust 需要存在:程序员编写内存不安全的代码。

首先,请阅读为什么在使用安全 Rust 时不允许这样做:

LinkedListElementTL;DR:移动时更改的内存地址。当从函数返回值时(以及其他时间),就会发生移动。通过使用原始指针,您破坏了借用检查器并且没有从编译器获得有用的反馈。

其次,请阅读通过完全太多的链表学习 Rust。无论出于何种原因,程序员都认为链表“简单”并且是学习语言的好方法。这在 Rust 中通常不是真的,因为内存安全是最重要的。

TL;DR:您可以使用 aBox在堆上分配内存。当指向它的指针移动时,这个内存地址不会改变。您需要确保在链表超出范围时适当地释放指针以防止内存泄漏。

也可以看看:

于 2019-05-15T02:51:15.327 回答