Почему узел в связанном списке с использованием необработанных указателей поврежден? - PullRequest
0 голосов
/ 15 мая 2019

Я изо всех сил пытаюсь выучить необработанные указатели при реализации связанного списка.Простой фрагмент кода дает мне непредвиденные результаты, для которых я изо всех сил пытаюсь найти какое-либо объяснение:

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 не идиоматичен но я все же хочу их выучить.

1 Ответ

5 голосов
/ 15 мая 2019

Поздравляем, вы успешно доказали, почему Rust должен существовать в первую очередь: программисты пишут небезопасный для памяти код.

Сначала, пожалуйста, прочитайте, почему это запрещено при использовании безопасного Rust:

TL; DR: адрес памяти LinkedListElement изменяется при перемещении,Перемещение происходит, когда значение возвращается из функции (среди прочего).Используя необработанный указатель, вы отменили проверку заимствований и не получили никакой полезной обратной связи от компилятора.

Во-вторых, прочитайте Изучение ржавчины со слишком большим количеством связанных списков .По какой-то причине программисты считают, что связанные списки являются «легким» и хорошим способом изучения языка.Обычно это неверно в Rust, где безопасность памяти имеет первостепенное значение.

TL; DR: вы можете использовать Box для выделения памяти в куче.Этот адрес памяти не изменится при перемещении указателя на него.Вам нужно будет убедиться, что вы соответствующим образом освободили указатель, когда ваш связанный список выходит из области видимости, чтобы предотвратить утечки памяти.

См. Также:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...