【问题标题】:Rust what data hold a variable which has assigned a struct to itRust 哪些数据包含一个已分配结构的变量
【发布时间】:2021-01-16 12:57:43
【问题描述】:

如果我们有以下代码:

struct Person {
    age: i32
}

fn main() {
    let person =  Person{age : 52};
}

我知道如何使用 struct 以及 person 变量中的所有内容?

它是指向结构第一个元素的指针吗? (因为所有元素都是连续的(?)所以编译器会知道所有元素在哪里)

【问题讨论】:

  • 没有指针:Person 保存实际数字,person 变量也是如此。例如,您可以获取person 的地址,将其转储,然后发现数字 52 存储在那里,而不是指向它的某个指针。
  • 相比之下,在以下声明中:let person1 = &Person { ... }; let person2 = Box::new(Person { ... }); let person3 = Rc::new(Person { ... }) 每个person* 变量确实包含一个指针。

标签: struct rust


【解决方案1】:

person 变量里面到底有什么?

“里面”唯一的东西是age,总共4个字节。

它是指向结构第一个元素的指针吗?

不,它不是指针。它只是值本身,这意味着它将存储在当前帧/函数的堆栈中,实际上(如果可观察的行为没有改变,编译器可能会完全从内存中删除它甚至不使用它,但是是一种优化:从概念上讲,变量放在堆栈中)。

(因为所有元素都是连续的(?)所以编译器会知道所有元素在哪里)

大多数编译语言中的编译器总是知道所有类型的确切布局(特殊情况除外)。这就是可以提前在这些语言中进行许多优化的原因。这些信息在系统编程语言中通常会被丢弃,这就是为什么它们通常不支持反射以及很难将它们反编译回源代码的原因。

【讨论】:

    【解决方案2】:

    Looks like a pointer indeed.

    编辑:从 Rust 的角度来看,person 包含实际的 struct,因此在 Rust 代码 let person = Person{age : 52}; 中,变量 person 不是指针并且不是 包含一个指针。但是,它可以实现为指针,如下面的 LLVM IR 所示。因此,Rust 代码可以转换为 LLVM IR,其中 %person 确实是指向结构第一个元素的指针。请注意,此 IR 可以通过这样一种方式进行优化,即实际数据可以最终存储在寄存器中,因此不一定在堆栈中。

    main 的 LLVM IR 如下所示:

    ; playground::main
    ; Function Attrs: nonlazybind uwtable
    define internal void @_ZN10playground4main17h5b277f290810a924E() unnamed_addr #1 !dbg !315 {
    start:
      %arg0.dbg.spill = alloca i32*, align 8
      %_11 = alloca i32*, align 8
      %_10 = alloca [1 x { i8*, i64* }], align 8
      %_3 = alloca %"std::fmt::Arguments", align 8
      %person = alloca i32, align 4
      call void @llvm.dbg.declare(metadata i32* %person, metadata !319, metadata !DIExpression()), !dbg !328
      store i32 52, i32* %person, align 4, !dbg !329
      // ...
    }
    
    • %person = alloca i32, align 4i32 on the stack and returns a pointer 分配空间(所以 person 是一个指针)
    • store i32 52, i32* %person, align 4 将整数 52 存储到该指针中。代码i32* %person 表示%person 的类型为i32*,因此又是一个指向整数的指针。

    如果将结构更改为struct Person { age: i32, thing: bool },例如,对应的IR 将是%person = alloca { i32, i8 }, align 4,所以现在它是一个指向{ i32, i8 } 类型结构的指针。

    现在存储整数 52 需要一些转换:

    %0 = bitcast { i32, i8 }* %person to i32* // cast %person to i32*
    store i32 52, i32* %0, align 4
    

    【讨论】:

    • 这个答案具有误导性:person 变量不包含指针,它包含 Person 结构,即实际数字。存储数字的位置取决于优化级别 - 在更高的优化级别上,它很可能最终出现在寄存器中,或者被完全消除。 alloca 指令和结果指针是编译器使用的 intermediate 表示的产物。最终的表示,即实际的机器代码,可能无法区分Personi32
    • 虽然有人可能会争辩说person,当存储在堆栈上时,在某种意义上是一个指针,因为处理器必须取消引用堆栈指针才能访问它,同样可以说@ 987654348@ 也是如此,而且通常没有人会声称 i32 变量以某种方式持有“指针”。
    • LLVM IR 是编译器的实现细节。说person“包含指针”是不正确的,因为它导致 OP 认为 Rust 结构像 Java 或 Python 对象一样实现,其中变量真正包含对堆分配对象的引用。 Rust 很清楚 person 包含实际数据:您可以获取 person 的地址并将其转储以找到数字 52,而不是指针。 CPU 可能会使用堆栈指针来访问该数据,但这适用于不在寄存器中且不是变量属性的任何数据。
    • @ForceBru 问题不在于 LLVM IR 如何表示堆栈中的对象或典型架构如何使用堆栈寄存器和偏移寻址。即使没有任何优化,变量本身也永远不会是指针。
    • LLVM IR 是一个实现细节似乎很清楚 - 从您的回答(在编辑之前)中根本不清楚,这给人完全相反的印象并且代表不正确rust 变量的语义。您的回答清楚地表明 person 包含一个指针,这在 Rust(以及具有相同内存模型的其他语言,如 C 和 C++)中显然是错误的。
    猜你喜欢
    • 1970-01-01
    • 2020-09-15
    • 2014-09-25
    • 1970-01-01
    • 1970-01-01
    • 2018-03-29
    • 2018-09-29
    • 2020-07-18
    • 2021-04-04
    相关资源
    最近更新 更多