如果你对我漫无边际地谈论我不懂的东西不感兴趣,请在底部检查TLDR:)
我正在尝试在Rust中实现A-star寻路算法。只是一个基本版本。我不想使用Rc
,我的节点存储*const
(原始指针)到以前的Node
(prev: const* Node
),以便我可以检索最短路径(解决方案)。
struct Node {
position: Position,
// NonNull also works I guess
previous: Option<*const Node>
}
另外,我使用的是PriorityQueue
(crate 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
结构体被移动时会发生什么?那些指向被移动的previous
Node
的指针是否无效?
1条答案
按热度按时间h43kikqp1#
当一个节点被移动时,它实际上被存储在其他地方,因此它的地址是不相同的(通常,即使一些优化可以防止这一点,但没有什么是保证的)。
下面的最小示例说明了这一点;在栈上创建初始节点,然后将它们移到向量的堆分配存储器中。显示所有这些节点的地址表明这些地址实际上并不相同。
n1
的previous
成员在堆栈中时初始化为n0
,当n0
移动到向量时,n0
的位置就变成了一个悬空指针。n0
的位置可能会被其他变量重用,当通过指针访问这个内存位置时会发生什么实际上是未定义的。这就是为什么在Rust中取消引用一个原始指针是一个
unsafe
操作的原因(这也可能是Rust存在的原因)。独立于
Copy
特性,复制值和移动值之间的区别是细微的。(一个整数和一个原始指针,如果None
,则最终为null),并且 * 不 * 使用此指针执行内存管理(就像Vec
对它所包含的指针所做的那样)。因此,移动Node
恰好与复制它在物理上是一样的:类似于Node
为Copy
的结构成员的按位副本。例如,当我们要移动
Vec
时,移动操作会更有趣。(移动到的位置)将指针(按位复制)到初始位置中已知的堆分配存储(它被移出的位置)。然后,这个移出的位置不再被认为是这个堆分配的存储的所有者:我们已经在moved-from和moved-to位置之间转移了堆分配存储的所有权,但我们不必复制此堆分配存储。这是复制/克隆上的移动操作的主要目的(通过简单地转移所有权来保存堆分配存储的复制)。在下面的示例中,当nodes
移动到nodes_again
时,可以看到这种情况:Vec
本身被移动了,但是它包含的Node
没有被移动。The Rust Programming Language的这一节很好地解释了这种情况,使用了一个String
而不是一个Vec
。