Почему двоичное дерево, использующее небезопасный код, имеет плохой доступ к памяти в режиме отладки, но не выпускается?

Я пытаюсь реализовать двоичное дерево в небезопасном Rust, и похоже, что это разница между отладкой и выпуском.

Этот код, выполняемый при отладке, с большой вероятностью получит доступ к неправильному адресу памяти, но если он скомпилирован в режиме выпуска, все в порядке.

Вполне возможно, что я совершил ошибку, поскольку я новичок в необработанных указателях, но иметь другой вывод действительно странно.

Действительно ли мой другой вывод свидетельствует о неправильном доступе к памяти? Ожидается ли это при работе с небезопасным Rust? Это как-то признак запаха кода?

В режиме отладки вывод на моем компьютере:

constructing tree
5
constructed
0.000000000000000000000000000000000000000000001
value added

В режиме выпуска вывод на моем компьютере:

constructing tree
5
constructed
5
value added

Вот код, максимально сокращенный.

use std::ptr;

struct Node {
    value: f32,
    node_left: *mut Node,
    node_right: *mut Node,
}

impl Node {
    pub fn from_value(value: f32) -> Node {
        println!("{}", value);
        Node {
            value: value,
            node_left: ptr::null_mut(),
            node_right: ptr::null_mut(),
        }
    }

    fn get_value(&self) -> f32 {
        self.value
    }
}

pub struct BinaryTree {
    root: *mut Node,
}

impl BinaryTree {
    pub fn from_value(value: f32) -> BinaryTree {
        let mut node = &mut Node::from_value(value);
        BinaryTree { root: node }
    }

    pub fn add(&mut self, value: f32) {
        println!("{}", unsafe { self.root.as_mut() }.unwrap().get_value());
    }
}

fn main() {
    println!("constructing tree");
    let mut x = BinaryTree::from_value(5.0f32);
    println!("constructed");
    x.add(2f32);
    println!("value added");
}

Я запускал это на Ubuntu 18.04 на виртуальной машине Oracle с использованием Rust 1.32.0.


person RomainL.    schedule 22.02.2019    source источник
comment
Иногда в rustc может быть ошибка ... но это очень-очень-очень маловероятно, если ваш код содержит небезопасный.   -  person Stargateur    schedule 22.02.2019
comment
Вы берете адрес стека, этот указатель недействителен после завершения функции.   -  person Stargateur    schedule 22.02.2019
comment
Какова ваша мотивация использовать указатели в этом случае? Поступая таким образом, вы, по сути, обходите все меры безопасности, которые предоставляет Rust.   -  person loganfsmyth    schedule 22.02.2019
comment
Спасибо за комментарий, так что я должен вернуть окно ‹Node› из функции? Моя мотивация: научиться играть с ptr и научиться реализовывать общую структуру данных. в дереве ржавчины или связанном списке кажется действительно тяжелым, чтобы сохранить всю безопасность.   -  person RomainL.    schedule 22.02.2019
comment
Например, вы можете использовать Option<Box<Node>> для своих дочерних узлов.   -  person loganfsmyth    schedule 22.02.2019
comment
дерево или связанный список кажется очень сложным для создания, сохраняя всю безопасность - да, потому что создание безопасных, но эффективных структур данных в целом сложно. Вот почему мы не реализуем их заново.   -  person Shepmaster    schedule 24.02.2019


Ответы (1)


В BinaryTree::from_value вы создаете новый Node, а затем сохраняете на него указатель. Однако Node выделяется в стеке и удаляется перед вызовом BinaryTree::add. Поскольку вы используете указатели и unsafe вместо ссылок, компилятор Rust не может предупредить вас о подобных проблемах времени жизни.

Что касается того, почему это не удается в режиме отладки, но работает в режиме выпуска, это может быть связано с оптимизацией, которая включена только для режима выпуска.

person apetranzilla    schedule 22.02.2019
comment
оптимизация, которая доступна только для режима выпуска - да, очень вероятно, что функция встроена, и поэтому стек не всплывает. - person Shepmaster; 24.02.2019