Box::new() 会从堆栈复制到堆吗?

yus*_*hao 23 rust

从文档中可以看出:

pub fn new(x: T) -> Box<T>
Run Code Online (Sandbox Code Playgroud)

在堆上分配内存,然后放入x其中。

但“地点”是一个棘手的词。如果我们写

let arr_boxed = Box::new([0;1000]);
Run Code Online (Sandbox Code Playgroud)

[0;1000]在堆上就地初始化吗?

如果我们写

let arr = [0;1000];
let arr_boxed = Box::new(arr);
Run Code Online (Sandbox Code Playgroud)

[0;1000]编译器是否足够聪明,可以首先在堆上初始化?

Chr*_*isB 22

Box::new() 会从堆栈复制到堆吗?

有时。Rust 语言不保证这种优化的发生,并且似乎将其留给 LLVM 来解决。因此,如果先初始化数组然后传递它根本没有关系,因为这对于后端来说本质上是相同的。

在实践中,性能将取决于具体情况。您给出的示例实际上很特殊,因为数据全为零:

pub fn foo() -> Box<[i32; 1000]> {
    return Box::new([0; 1000]);
}
Run Code Online (Sandbox Code Playgroud)

在我的测试中,编译器能够将其转换为分配+对memset堆数据的调用。

注意:仅在打开优化的情况下。在调试模式下它将复制。


另一方面,您可能希望使用已知值初始化数据:

pub fn bar(v: i32) -> Box<[i32; 1000]> {
    return Box::new([v; 1000]);
}
Run Code Online (Sandbox Code Playgroud)

令我恐惧的是,编译器决定初始化堆栈上的整个数据,然后调用memcpy. (至少它展开了填充循环):)。即使对于像这样的非常大的数据也会发生这种情况[v; 100000],这会因堆栈溢出而使您的程序崩溃。使用编译时已知(非零)文字的[64; 100000]行为方式相同。


如果你真的想确定,你可以这样做:

pub fn baz(v: i32) -> Box<[i32; 1000]>{
    unsafe {
        let b = std::alloc::alloc(
            std::alloc::Layout::array::<i32>(1000).unwrap_unchecked()
        ) as *mut i32;
        for i in 0..1000 {
            *b.add(i) = v;
        }
        Box::from_raw(b as *mut [i32; 1000])
    }
}
Run Code Online (Sandbox Code Playgroud)

这是正确的做法。


一个安全版本baz是:

pub fn baz(v: i32) -> Box<[i32; 1000]>{
    unsafe {
        let b = std::alloc::alloc(
            std::alloc::Layout::array::<i32>(1000).unwrap_unchecked()
        ) as *mut i32;
        for i in 0..1000 {
            *b.add(i) = v;
        }
        Box::from_raw(b as *mut [i32; 1000])
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器对其进行了很好的优化,基本上与baz.


甚至更短

vec![v; 1000].into_boxed_slice().try_into::<Box<[i32; 1000]>>().unwrap()

这可能是最好的版本。

  • `quux()` 可以使用 `vec!` 并且只返回 `vec![v; [第 1000 章].into_boxed_slice()`. (5认同)
  • 对于最后一个,您还可以添加 .try_into().unwrap() 来获取 Box&lt;[i32; 1000]&gt;`。结果与不安全的结果相同!我没想到会这样。https://godbolt.org/z/aqzqK8jo1 (3认同)
  • 有一个夜间功能“new_uninit”,它允许创建一个包含未初始化的“MaybeUninit&lt;T&gt;”的“Box”,然后调用_safe_“write”函数来覆盖“Box”的内容并使其成为“Box&lt;T&gt;” ` 同时参见 https://play.rust-lang.org/?version=nightly&amp;mode=release&amp;edition=2021 。仍然需要优化,但是_哦比“baz”安全得多_。 (3认同)