首页 文章

当堆栈分配值被装箱时会发生什么?

提问于
浏览
5

如果我们有一个已经在堆栈上分配的值,那么装箱会将它复制到堆中,然后转移所有权(这就是它在.NET中的工作方式,除了两个副本都能保持活动状态)?或者编译器是否“足够”以便从一开始就直接在堆上分配它?

struct Foo {
    x: i32,
}

fn main() {
    // a is allocated on stack?
    let a = Foo { x: 1 };

    // if a is not used, it will be optimized out
    println!("{}", a.x);

    // what happens here? will the stack allocated structure
    // be moved to heap? or was it originally allocated on heap?
    let b = Box::new(a);
}

我不是汇编程序的专家,但看起来它实际上是在堆栈上分配然后移动:http://pastebin.com/8PzsgTJ1 . 但我需要一个确实知道发生了什么的人的确认 .

2 回答

  • 3

    如你所描述的那样,这种优化会发生,这很奇怪 . 例如,在此代码中:

    let a = Foo { x: 1 };
    // operation that observes a
    let b = Box::new(a);
    // operation that observes b
    

    &a&b 将是平等的,这将是令人惊讶的 . 但是,如果你做类似的事情,但不要观察 a

    #[inline(never)]
    fn frobnotz() -> Box<Foo> {
        let a = Foo { x: 1 };
        Box::new(a)
    }
    

    你可以see via the LLVM IR这个案例是优化的:

    define internal fastcc noalias dereferenceable(4) %Foo* @_ZN8frobnotz20h3dca7bc0ee8400bciaaE() unnamed_addr #0 {
    entry-block:
      %0 = tail call i8* @je_mallocx(i64 4, i32 0)
      %1 = icmp eq i8* %0, null
      br i1 %1, label %then-block-106-.i.i, label %"_ZN5boxed12Box$LT$T$GT$3new20h2665038481379993400E.exit"
    
    then-block-106-.i.i:                              ; preds = %entry-block
      tail call void @_ZN3oom20he7076b57c17ed7c6HYaE()
      unreachable
    
    "_ZN5boxed12Box$LT$T$GT$3new20h2665038481379993400E.exit": ; preds = %entry-block
      %2 = bitcast i8* %0 to %Foo*
      %x.sroa.0.0..sroa_idx.i = bitcast i8* %0 to i32*
      store i32 1, i32* %x.sroa.0.0..sroa_idx.i, align 4
      ret %Foo* %2
    }
    

    类似地,您可以在堆栈上返回结构,然后将其打包,仍然会just be the one allocation

    您可能认为这会给我们带来可怕的表现:返回一个值,然后立即将其打包?!这种模式不是两个世界中最糟糕的吗? Rust比那更聪明 . 此代码中没有副本 . main为盒子分配足够的空间,将指向该内存的指针作为x传递给foo,然后foo将值直接写入Box .

  • 2

    正如官方Rust文档here中所述, Box<T>::new(x: T) 在堆上分配内存,然后将参数移动到该内存中 . 在 let b = Box::new(a) 之后访问 a 是编译时错误 .

相关问题