我有以下程序:
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);
}
这是示例输出:
Example 1: 0x7ffcb4e72144
Example 1: 0x7ffcb4e72144
Example 2: 0x7ffcb4e72238
Example 2: 0x7ffcb4e72290
"Example 1"
的输出始终相同,而"Example 2"
的输出始终不同。
我已经读过Does println! borrow or own the variable?,从给出的答案中我了解到,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"
没有打印出不同的地址?
让我们开始一个棘手的问题:这是否可以编译?
fn main() {
println!("{:p}", 1i32);
}
我们正在要求打印i32
作为存储地址。这有意义吗?
不,当然,Rust正确拒绝了该程序。
error[E0277]: the trait bound `i32: std::fmt::Pointer` is not satisfied
--> src/main.rs:2:22
|
2 | println!("{:p}", 1i32);
| ^^^^ the trait `std::fmt::Pointer` is not implemented for `i32`
|
= note: required by `std::fmt::Pointer::fmt`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
但是我们知道宏隐式借用了参数,因此1i32
变为&1i32
。引用do实现Pointer
。那有什么关系?
首先,它有助于理解为什么宏借用其参数。您是否曾经注意到all the formatting traits看上去几乎完全相同?它们都完全定义了一个名为fmt
的方法,该方法带有两个参数&self
和&mut Formatter
并返回Result<(), fmt::Error>
。
这里是&self
。为了调用fmt
,我们只需要引用该值,因为格式化一个值不需要该值的所有权。现在,格式化参数的实现比这要复杂得多,但是最终,对于参数x
,程序最终将调用std::fmt::Pointer::fmt(&x, formatter)
(对于:p
)。但是,为使此调用成功编译,x
的类型必须实现Pointer
,not的类型必须&x
。如果x
为1i32
,则x
的类型为i32
,并且i32
不实现Pointer
。
结论是:p
格式最终将打印由程序中以文本形式编写的表达式表示的指针的值。在该表达式上借用的是那里,因此宏不会拥有该参数的所有权(这对:p
仍然有用,例如,如果您想打印Box<T>
)。
现在,我们可以继续说明您的程序的行为。 x
是局部变量。局部变量通常1具有稳定的地址2。在您的Example 1
调用中,表达式&x
允许我们观察该地址。两次出现&x
都会得到相同的结果,因为x
在两次调用之间没有移动。打印的是x
的地址(即保存值为0
的地址)。
但是,表达式&&x
有点奇怪。将地址两次重复到底意味着什么?子表达式&x
产生一个临时值,因为结果没有分配给变量。然后,我们询问该临时值的地址。 Rust is kind enough to let us do that,但这意味着我们必须将临时值存储在内存中的某个位置,以便它具有some地址。这里,临时值存储在一些隐藏的局部变量中。
事实证明,在调试版本中,编译器为两次出现的&x
中的每个&&x
子表达式创建了一个单独的隐藏变量。这就是为什么我们可以为Example 2
行观察两个不同的内存地址。但是,in release builds对代码进行了优化,以便仅创建一个隐藏变量(因为在需要第二个变量的时候,我们不再需要第一个变量,因此我们可以重用其内存位置),因此这两个Example 2
行实际上会打印相同的内存地址!
1我说通常是因为在某些情况下,优化器可能会决定在内存中移动局部变量。我不知道实际上是否有任何优化程序可以这样做。
2某些局部变量可能根本没有“地址”!如果从未观察到局部变量的地址,则优化器可能会决定将局部变量保留在寄存器中。在许多处理器体系结构上,寄存器不能用指针寻址,因为可以说,它们位于不同的“地址空间”中。当然,在这里,我们are观察地址,因此我们可以确信该变量实际上位于堆栈中。