println怎样!与多个间接级别交互?

问题描述 投票:1回答:1

我有以下程序:

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"没有打印出不同的地址?

pointers rust
1个回答
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的类型必须实现Pointernot的类型必须&x。如果x1i32,则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观察地址,因此我们可以确信该变量实际上位于堆栈中。

© www.soinside.com 2019 - 2024. All rights reserved.