我有以下程序:
fn main() {
let x = 0;
println!("Example 1: {:p}", &x);
println!("Example 1: {:p}", &x);
println!("Example 2: {:p}", &&x);
println!("Example 2: {:p}", &&x);
}
Run Code Online (Sandbox Code Playgroud)
这是一个示例输出:
Example 1: 0x7ffcb4e72144
Example 1: 0x7ffcb4e72144
Example 2: 0x7ffcb4e72238
Example 2: 0x7ffcb4e72290
Run Code Online (Sandbox Code Playgroud)
for 的输出"Example 1"始终相同,而 for的输出"Example 2"始终不同。
我已经阅读了是否 println! 借用或拥有变量?,而我从给定的答案中了解到的是,println!默默地参考。换句话说,这听起来像是println!增加了一个额外的间接级别。
我原以为输出"Example 1"也会有所不同。看到它println!悄悄地采取了另一个间接级别,"Example 1"实际上正在与 一起工作&&x,并且"Example 2"正在与 一起工作&&&x。这似乎与我链接的答案一致,特别是:"If you write println!("{}", &x), you are then dealing with two levels of references"。
我认为值&&x持有将打印为"Example 1",而值&&&x持有将打印为"Example 2"。双方&&x并&&&x持有临时&x,所以我想"Example 1"会有印刷以及不同的地址。
我哪里错了?为什么不"Example 1"打印不同的地址?
Fra*_*gné 10
让我们从一个棘手的问题开始:这是否编译?
fn main() {
println!("{:p}", 1i32);
}
Run Code Online (Sandbox Code Playgroud)
我们要求打印i32一个内存地址。这有意义吗?
不,当然,Rust 理所当然地拒绝了这个程序。
fn main() {
println!("{:p}", 1i32);
}
Run Code Online (Sandbox Code Playgroud)
但是我们知道宏隐式借用了参数,所以1i32变成了&1i32。并且引用确实实现了Pointer. 那么有什么关系呢?
首先,它有助于理解为什么宏借用它的参数。你有没有注意到所有的格式特征看起来几乎完全相同?它们都定义了一个方法,named fmt,它接受两个参数,&selfa&mut Formatter和返回Result<(), fmt::Error>。
这是&self相关的。为了调用fmt,我们只需要对值的引用,因为格式化值不需要该值的所有权。现在,格式化参数的实现比这更复杂,但最终,对于一个参数x,程序最终会调用std::fmt::Pointer::fmt(&x, formatter)(for :p)。但是,要成功编译此调用,x必须实现的类型Pointer,而不是的类型&x。如果x是1i32,则类型x是i32,并且i32不实现Pointer。
结论是该:p格式最终将打印由程序中以文本形式编写的表达式所表示的指针的值。该表达式的借用在那里,因此宏不会取得参数的所有权(这对于 仍然很有用:p,例如,如果您想打印 a Box<T>)。
现在我们可以继续解释您的程序的行为。x是局部变量。局部变量通常1有一个稳定的地址2。在您的Example 1调用中,表达式&x允许我们观察该地址。两次出现都&x将给出相同的结果,因为x在调用之间没有移动。打印的是地址x(即保存值的地址0)。
不过,表情&&x有点奇怪。取地址两次究竟是什么意思?子表达式&x产生一个临时值,因为结果没有分配给变量。然后,我们询问该临时值的地址。Rust 很友好地让我们这样做,但这意味着我们必须将临时值存储在内存中的某个地方,以便它有一些地址。在这里,临时值存储在一些隐藏的局部变量中。
事实证明,在调试版本中,编译器为&x两次出现的子表达式创建了一个单独的隐藏变量&&x。这就是为什么我们可以观察到这些Example 2行的两个不同的内存地址。然而,在 release builds 中,代码被优化为只创建一个隐藏变量(因为在我们需要第二个的时候,我们不再需要第一个,所以我们可以重用它的内存位置),所以两个Example 2行实际上打印相同的内存地址!
1我通常这么说是因为在某些情况下优化器可能会决定在内存中移动局部变量。我不知道是否有任何优化器在实践中确实做到了这一点。
2一些局部变量可能根本没有“地址”!如果从未观察到该变量的地址,优化器可能会决定将局部变量保存在寄存器中。在许多处理器体系结构上,寄存器不能通过指针寻址,因为可以这么说,它们位于不同的“地址空间”中。当然,在这里,我们正在观察地址,因此我们可以非常确信变量实际上存在于堆栈中。