如果我们有一个已经在堆栈上分配的值,那么装箱会将它复制到堆中然后转移所有权(这是如何在.NET中运行的,除了两个副本都能保持活动状态)?或者编译器是" smart"足以从一开始就直接在堆上分配它?
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。但我需要一个确实知道发生了什么的人的确认。
答案 0 :(得分: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为盒子分配足够的空间,将指向该内存的指针传递给foo作为x,然后foo将值直接写入Box。
答案 1 :(得分:2)
正如官方Rust文档here中所解释的那样,Box<T>::new(x: T)
在堆上分配内存,然后将参数移动到该内存中。在a
之后访问let b = Box::new(a)
是编译时错误。