"Rust Essentials"这本书关于变量在栈中或堆中的位置是否正确?

Is the book "Rust Essentials" correct about the location of variables in the stack or in the heap?

我在 Ivo Balbaert 的书 Rust Essentials 的第 2 章中找到了获取变量内存位置的代码 "Stack and Heap":

let a = 32;
let mut b = "str";
println!({:p} {:p}, &a, &b);

书中有输出0x23fba4 0x23fb90,它指出第一个地址是堆栈中的位置,第二个是堆中的位置。

我对这个说法有些怀疑,因为我听说堆栈地址是朝着内存地址递减的方向增长的。上面第二个地址好像是栈中的一个位置。

我错了吗?

引用:

Now, we will run the following program and try to visualize the program's memory: // see Chapter 2/code/references.rs

let health = 32;
let mut game = "Space Invaders";

Values are stored in memory and so they have memory addresses. The health variable contains an integer value 32 that is stored in the stack at location 0x23fba4, while the variable game contains a string, which is stored in the heap starting at location 0x23fb90. (These were the addresses when I executed the program, but they will be different when you run the program.)

The variables to which the values are bound are pointers or references to the values. They point to them; game is a reference to Space Invaders. The address of a value is given by the & operator. So, &health is the address where value 32 is stored, and &game is the address where the Space Invaders' value is stored. We can print these addresses by using the format string {:p} for pointers like this:

println!("address of health-value: {:p}", &health); // prints 0x23fba4
println!("address of game-value: {:p}", &game); // prints 0x23fb90

,书错了。作为变量 b 的胖指针位于堆栈中,就像 a 一样。只有它指向的字符串数据可以在其他地方。

在示例 let mut b = "str"; 中,字符串数据实际上离堆很远。它静态放置在您程序的 data segment 中。要真正将它放在堆上,我们需要使用 let b = String::from("str");。生成的内存如下图所示:

让我们手动检查内存,看看发生了什么。

假设 ab 位于地址 0x7ffeda6df61c 和 0x7ffeda6df620。

// print part of stack memory starting at &a
let m: &[u8] = unsafe {
    slice::from_raw_parts(&a as *const _ as *const u8, 4 + 16)
};
println!("{:?}", m);

输出看起来像这样:

[32, 0, 0, 0, 128, 85, 251, 177, 191, 85, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]

  • 32, 0, 0, 0a
  • 的四个字节
  • 128, 85, 251, 177, 191, 85, 0, 0b的第一部分,指向字符串数据
  • 的64位指针
  • 3, 0, 0, 0, 0, 0, 0, 0:第二部分b,字符串的长度

现在跟随数据指针:

// manually follow the data pointer
let address = unsafe {
    *(&b as *const _ as *const usize)
};
let p = address as *const u8;
println!("{:p}", p);  // 0x55bfb1fb5580

虽然 ab 位于同一内存区域 (0x7f...),但字符串数据位于不同的区域 (0x7e...)。

// print content of pointer
let s: &[u8] = unsafe {
    slice::from_raw_parts(p, 4)
};
println!("{:?}", s);  // [115, 116, 114, 32]

前三个字节包含 s、t 和 r 的 ASCII 码。第四个字节是任意垃圾。

下面是完整的代码。

use std::slice;

fn main() {
    let a: i32 = 32;
    let b = String::from("str");
    println!("{:p} {:p}", &a, &b);

    // print part of stack memory starting at a
    let m: &[u8] = unsafe {
        slice::from_raw_parts(&a as *const _ as *const u8, 4 + 16)
    };
    println!("{:?}", m);

    // manually follow the str pointer
    let address = unsafe {
        *(&b as *const _ as *const usize)
    };
    let p = address as *const u8;
    println!("{:p}", p);

    // print content of pointer
    let s: &[u8] = unsafe {
        slice::from_raw_parts(p, 4)
    };
    println!("{:?}", s);
}

请注意,代码示例采用 64 位指针并依赖于编译器的实现细节,并且可能在将来或在其他系统上中断。特别是,无法保证堆栈框架或 &str 的布局。请不要在实际代码中使用任何这些:)