当不是Copy的值被移动时,指向被移动值的原始指针会发生什么变化?(Rust)

332nm8kg  于 2022-11-12  发布在  其他
关注(0)|答案(1)|浏览(137)

如果你对我漫无边际地谈论我不懂的东西不感兴趣,请在底部检查TLDR:)
我正在尝试在Rust中实现A-star寻路算法。只是一个基本版本。我不想使用Rc,我的节点存储*const(原始指针)到以前的Nodeprev: const* Node),以便我可以检索最短路径(解决方案)。

struct Node {
    position: Position,
    // NonNull also works I guess
    previous: Option<*const Node>
}

另外,我使用的是PriorityQueuecrate link)来存储节点,我对内存管理和底层的知识非常有限。
好的,现在我将首先谈一谈我的程序内部发生了什么,以便更好地阐明我所问的问题。
据我所知,我的节点被存储/移动PriorityQueue中,实际上是一个HashMap
一个月七个月一个月-〉一个月八个月一个月-〉一个月九个月一个月-〉一个月十个月一个月
因此,它们存储在heap上,但问题是,我的节点首先在stack上创建(我从不使用Box对它们进行堆分配,或者它们的生存期只是绑定到堆栈上,我猜是这样?)
然后它们的previous字段被分配一个指向前一个节点*const Node的原始指针,然后它们被移动/推入从函数返回的vector(现在它们在堆上)。

节点stack-〉Vec

返回Vec的函数代码:

fn neighbours(&self, maze: &Maze) -> Vec<Node> {
    let offset_x: [isize; 8] = [-1, -1, 0, 1, 1, 1, 0, -1];
    let offset_y: [isize; 8] = [0, -1, -1, -1, 0, 1, 1, 1];

    let mut neighbours = Vec::new();

    for i in 0..8 {
        let nx = self.position.x() + offset_x[i];
        let ny = self.position.y() + offset_y[i];

        if Node::is_valid((nx, ny), maze) {
            let (nx, ny) = (nx as usize, ny as usize);

            let mut node = Node::new(Position((nx, ny)), self, maze.end().unwrap());

            node.previous = Some(self as *const _);
            neighbours.push(node);
        } else {
            continue;
        }
    }
    neighbours
}

请记住,当我们将指向*const Node的原始指针(*const Node)赋给新创建的节点时,previous节点位于堆栈上。(还有旁注,Previous节点最初是从PriorityQueue中弹出的,并移动stack上的一个变量中)

上一个:stack

之后,(节点)从该向量中移动PriorityQueue中,我们将此队列称为open(或者只是被丢弃,但这并不重要)。

节点:Vec-〉open: PriorityQueue

到目前为止一切顺利,因为Previous节点尚未移动,原始指针仍应指向它。
但是在这些节点进入PriorityQueue之后,Previous节点也被移动到另一个PriorityQueue中,我们称之为队列closed

上一个:stack-〉closed: PriorityQueue

现在我的最后一个问题是,指向前一个节点的原始指针会发生什么变化,这是一种未定义的行为吗,这意味着现在的指针不再指向那个分配?

顶级域名注册商

非复制值移动时内存如何工作(它是否仍在相同的内存地址上)?是否只有值的所有权发生了变化,而值仍在相同的内存地址上,或者它更改了内存地址,获得了新的内存分配,只是被复制到了那里?如果这可能有点过于一般化,那么当我的Node结构体被移动时会发生什么?那些指向被移动的previousNode的指针是否无效?

h43kikqp

h43kikqp1#

当一个节点被移动时,它实际上被存储在其他地方,因此它的地址是不相同的(通常,即使一些优化可以防止这一点,但没有什么是保证的)。
下面的最小示例说明了这一点;在栈上创建初始节点,然后将它们移到向量的堆分配存储器中。显示所有这些节点的地址表明这些地址实际上并不相同。
n1previous成员在堆栈中时初始化为n0,当n0移动到向量时,n0的位置就变成了一个悬空指针。n0的位置可能会被其他变量重用,当通过指针访问这个内存位置时会发生什么实际上是未定义的
这就是为什么在Rust中取消引用一个原始指针是一个unsafe操作的原因(这也可能是Rust存在的原因)。
独立于Copy特性,复制值和移动值之间的区别是细微的。(一个整数和一个原始指针,如果None,则最终为null),并且 * 不 * 使用此指针执行内存管理(就像Vec对它所包含的指针所做的那样)。因此,移动Node恰好与复制它在物理上是一样的:类似于NodeCopy的结构成员的按位副本。
例如,当我们要移动Vec时,移动操作会更有趣。(移动到的位置)将指针(按位复制)到初始位置中已知的堆分配存储(它被移出的位置)。然后,这个移出的位置不再被认为是这个堆分配的存储的所有者:我们已经在moved-from和moved-to位置之间转移了堆分配存储的所有权,但我们不必复制此堆分配存储。这是复制/克隆上的移动操作的主要目的(通过简单地转移所有权来保存堆分配存储的复制)。在下面的示例中,当nodes移动到nodes_again时,可以看到这种情况:Vec本身被移动了,但是它包含的Node没有被移动。The Rust Programming Language的这一节很好地解释了这种情况,使用了一个String而不是一个Vec


# [derive(Debug)]

struct Node {
    value: i32,
    previous: Option<*const Node>,
}

fn main() {
    let n0 = Node {
        value: 0,
        previous: None,
    };
    let n1 = Node {
        value: 1,
        previous: Some(&n0 as *const _),
    };
    println!("n0 at {:?}: {:?}", &n0 as *const _, n0);
    println!("n1 at {:?}: {:?}", &n1 as *const _, n1);
    println!("~~~~~~~~");
    let nodes = vec![n0, n1];
    println!("nodes at {:?}", &nodes as *const _);
    for (i, n) in nodes.iter().enumerate() {
        println!("nodes[{}] at {:?}: {:?}", i, n as *const _, n);
    }
    println!("~~~~~~~~");
    let nodes_again = nodes;
    println!("nodes_again at {:?}", &nodes_again as *const _);
    for (i, n) in nodes_again.iter().enumerate() {
        println!("nodes_again[{}] at {:?}: {:?}", i, n as *const _, n);
    }
}
/*
n0 at 0x7ffd2dd14520: Node { value: 0, previous: None }
n1 at 0x7ffd2dd14500: Node { value: 1, previous: Some(0x7ffd2dd14520) }
~~~~~~~~
nodes at 0x7ffd2dd144e8
nodes[0] at 0x558bad50fba0: Node { value: 0, previous: None }
nodes[1] at 0x558bad50fbb8: Node { value: 1, previous: Some(0x7ffd2dd14520) }
~~~~~~~~
nodes_again at 0x7ffd2dd14490
nodes_again[0] at 0x558bad50fba0: Node { value: 0, previous: None }
nodes_again[1] at 0x558bad50fbb8: Node { value: 1, previous: Some(0x7ffd2dd14520) }

* /

相关问题