结构变量的移动是“移动”,但分配了内存中的新空间并且基础值的地址不同

dow*_*123 3 rust borrow-checker

我试图在包含具有实现该Copy特征的类型的字段的结构的情况下理解 Rust 的移动语义。考虑以下结构:

\n
struct Book {\n    author: &\'static str,\n    title: &\'static str,\n    year: u32,\n}\n\nfn main() {\n    let bookA = Book {\n        author: "Douglas Hofstadter",\n        title: "G\xc3\xb6del, Escher, Bach",\n        year: 1979,\n    };\n    \n    let addr_bookA: *const Book = &bookA;\n    let bookB = bookA; // Move occurs here\n    \n    // Getting the memory addresses as raw pointers\n    \n    let addr_bookB: *const Book = &bookB;\n    \n    println!("Address of bookA: {:p}", addr_bookA);\n    println!("Address of bookB: {:p}", addr_bookB);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

输出:

\n
Address of bookA: 0x7fff20b1a0b0\nAddress of bookB: 0x7fff20b1a0e0\n
Run Code Online (Sandbox Code Playgroud)\n

假设有一个移动,并且 bookB 占用了分配给 bookA 的值,我期望在bookA分配给时获得数据的浅表副本bookB。然而,当我检查这两个变量的内存地址时,它们是不同的。

\n

有人可以帮助澄清以下几点吗?

\n
    \n
  1. 为什么结构体的移动仍然会导致原始变量和移动到的变量的内存地址不同?

    \n
  2. \n
  3. 在这种情况下数据是否真的被复制了?如果是的话,为什么 Rust 将其视为一次移动?

    \n
  4. \n
\n

操场

\n

Mas*_*inn 6

在这种情况下数据是否真的被复制了?如果是的话,为什么 Rust 将其视为一次移动?

因为移动和副本之间的唯一区别在于,副本仍然可以使用原始副本。从语义上讲,两者都是 memcpy(因此将源字节复制到目标)。

这些副本可能会被优化掉,但这与语义无关。

为什么结构体的移动仍然会导致原始变量和移动到的变量的内存地址不同?

因为 LLVM 不会优化掉副本。我承认,我不知道为什么,我曾经假设 LLVM 不会看到同一事物的 SSA 版本之间的差异,但显然它确实看到了,并且拒绝或无法通过删除副本来优化其堆栈分配(只需重新标记)内部)。我从来没有深入研究过为什么这种情况不会发生,可能有一些 rustc 没有设置的元数据,或者一些安全的极端情况阻止了 LLVM 这样做。

  • 我打算指出,获取两个变量的地址可以防止移动省略(否则会违反假定规则),但即使不获取地址,LLVM 也不会删除移动。奇怪的。 (3认同)

cdh*_*wie 5

为什么结构体的移动仍然会导致原始变量和移动到的变量的内存地址不同?

很简单,因为您观察了地址。这迫使优化器实现两个不同的位置来保存数据;获取地址的行为本身会阻止优化器忽略移动。(在您的情况下,您将它们打印出来,但优化器可能无法弄清楚您将它们交给执行格式化的函数不会取消引用它们。)

正如法恩斯沃斯教授曾经说过的:

不公平! 您通过测量改变了结果

如果删除获取和打印地址的代码,然后检查生成的程序集,您将看到没有为 move 生成​​代码。(此处的突出显示具有误导性,因为该行let bookB = bookA显示了生成的代码,但请注意,初始化行bookA并未显示。如果您通过移动删除该行,则生成的程序集将是相同的。优化器似乎只是将初始化延迟到该行随着移动,因此它将生成的程序集与该行相关联,而不是与初始化的行相关联bookA。)

如果您好奇正在执行哪些优化,您应该直接检查生成的程序集。修改代码以尝试确定正在执行哪些优化可能会更改所执行的优化。

  • 我实际上不确定假设规则是否适用于此。这两个值永远不会同时存在,因此不需要它们的地址不同。这可能是一个错失的优化机会。 (2认同)