首页 > 解决方案 > println! 与多个间接级别交互?

问题描述

我有以下程序:

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

for 的输出"Example 1"始终相同,而 for的输出"Example 2"始终不同。

我读过是否打印!借用还是拥有变量?,而我从给定的答案中了解到的是,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"打印不同的地址?

标签: pointersrust

解决方案


让我们从一个棘手的问题开始:这是否编译?

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。并且引用确实实现了Pointer. 那么有什么关系呢?

首先,它有助于理解为什么宏借用了它的论点。您是否注意到所有格式特征看起来几乎相同?它们都定义了一个方法,named fmt,它接受两个参数,&selfa&mut Formatter和 return Result<(), fmt::Error>

&self是相关的。为了调用fmt,我们只需要对该值的引用,因为格式化一个值不需要该值的所有权。现在,格式化参数的实现比这更复杂,但最终,对于一个参数x,程序最终会调用std::fmt::Pointer::fmt(&x, formatter)(for :p)。但是,要成功编译此调用,x必须实现的类型Pointer而不是的类型&x。如果x1i32,那么类型xi32,并且i32不实现Pointer

结论是该:p格式最终会打印出程序中以文本形式编写的表达式所表示的指针的值。该表达式上的借用存在,因此宏不获取参数的所有权(这对于 仍然有用:p,例如,如果您想打印 a Box<T>)。


现在我们可以继续解释程序的行为。x是一个局部变量。局部变量通常1有一个稳定的地址2。在您的Example 1电话中,该表达式&x允许我们观察该地址。两次出现&x都会给出相同的结果,因为x在调用之间没有移动。打印的是地址x(即保存值的地址0)。

不过,表情&&x有些好奇。取地址两次究竟是什么意思?子表达式&x产生一个临时值,因为结果未分配给变量。然后,我们询问该临时值的地址。Rust 很友好地让我们这样做,但这意味着我们必须将临时值存储在内存中的某个位置,以便它有一些地址。在这里,临时值存储在一些隐藏的局部变量中。

事实证明,在调试版本中,编译器为&x两次出现的&&x. 这就是为什么我们可以观察到两个不同的内存地址Example 2。但是,在发布版本中,代码被优化,因此只创建了一个隐藏变量(因为在我们需要第二个的时候,我们不再需要第一个,所以我们可以重用它的内存位置),所以两个Example 2行实际上打印相同的内存地址!


1我之所以这么说,因为在某些情况下,优化器可能会决定在内存中移动一个局部变量。我不知道是否有任何优化器实际上在实践中这样做。

2一些局部变量可能根本没有“地址”!如果从未观察到该变量的地址,则优化器可能会决定将局部变量保留在寄存器中。在许多处理器架构上,寄存器不能通过指针来寻址,因为可以说它们位于不同的“地址空间”中。当然,在这里,我们正在观察地址,因此我们可以非常确信变量实际上存在于堆栈中。


推荐阅读