【问题标题】:How to allocate structs on the heap without taking up space on the stack in stable Rust?如何在稳定的 Rust 中分配堆上的结构而不占用堆栈上的空间?
【发布时间】:2023-03-27 17:15:02
【问题描述】:

在这段代码中...

struct Test { a: i32, b: i64 }
    
fn foo() -> Box<Test> {              // Stack frame:
    let v = Test { a: 123, b: 456 }; // 12 bytes
    Box::new(v)                      // `usize` bytes (`*const T` in `Box`)
}

...据我了解(忽略可能的优化),v 在堆栈上分配,然后复制到堆中,然后在 Box 中返回。

还有这段代码……

fn foo() -> Box<Test> {
    Box::new(Test { a: 123, b: 456 })
}

...应该没有什么不同,大概是因为应该有一个用于结构分配的临时变量(假设编译器对Box::new() 中的实例化表达式没有任何特殊语义)。

我找到了Do values in return position always get allocated in the parents stack frame or receiving Box?。关于我的具体问题,它只提出了实验性的box 语法,但主要谈论编译器优化(复制省略)。

所以我的问题仍然存在:使用 stable Rust,如何在不依赖编译器优化的情况下直接在堆上分配 structs?

【问题讨论】:

  • 这将是一件完全荒谬的事情,让编译器为你施展魔法。但是如果你想要doc.rust-lang.org/std/alloc/trait.Alloc.html.
  • 感谢您的链接。但是,我强烈反对荒谬。我认为任何“魔法”本质上都是不可靠的,更不用说它需要编译器内部知识了。此外,当性能至关重要或资源有限时,所有内存操作都很重要,在这种特殊情况下,最好确保不执行不必要的操作。
  • 我还应该注意,即使提供的链接在技术上解决了问题,我很确定这不是惯用的方式。只是水平太低了。我本可以直接为此调用分配器,并获得指向堆的原始指针。我很确定为此必须有一个语言级别(或“stdlib 级别”)的构造。
  • 另一个“解决方案”是使用doc.rust-lang.org/std/boxed/struct.Box.html#method.new_uninit 那个新的所以我以前没有想到它,在我看来仍然很低。但是用它来“避免使用堆栈”是愚蠢的。

标签: rust


【解决方案1】:

您似乎正在寻找 box_syntax 功能,但是从 Rust 1.39.0 开始,它并不稳定,只能在夜间编译器中使用。看起来这个功能不会很快稳定下来,如果它稳定下来,可能会有不同的设计。

在夜间编译器上,您可以编写:

#![feature(box_syntax)]

struct Test { a: i32, b: i64 }

fn foo() -> Box<Test> {
    box Test { a: 123, b: 456 }
}

【讨论】:

  • 这是一个很好的答案。然而,我问的是 stable Rust,事实证明,它有一个解决方案,尽管没有这种语法那么优雅。而且,由于这种语法基本上是编译器内部的特性(github.com/rust-lang/rust/issues/49733),我认为稳定的解决方案会持续很长时间。
【解决方案2】:

从 Rust 1.39 开始,似乎只有一种稳定的方式可以直接在堆上分配内存 - 使用 std::alloc::alloc(请注意,文档声明它预计会被弃用)。这是相当不安全的。

例子:

#[derive(Debug)]
struct Test {
    a: i64,
    b: &'static str,
}

fn main() {
    use std::alloc::{alloc, dealloc, Layout};

    unsafe {
        let layout = Layout::new::<Test>();
        let ptr = alloc(layout) as *mut Test;

        (*ptr).a = 42;
        (*ptr).b = "testing";

        let bx = Box::from_raw(ptr);

        println!("{:?}", bx);
    }
}

这种方法用于不稳定的方法Box::new_uninit

事实证明,甚至还有一个板条箱可以避免memcpy 调用(除其他外):copyless。这个 crate 也使用了基于 this 的方法。

【讨论】:

  • 不应该使用std::ptr::write 而不是用= 赋值来避免在该位置删除旧值吗?
  • 此代码在尝试分配 ab 的值时会导致未定义的行为,因为它构造了对字段 ab 的引用,但那些有未初始化的垃圾他们。在raw references 稳定之前,无法正确执行此操作。
  • @OptimisticPeach 会更好,但在这种特定情况中不需要它,因为i64&amp;str 没有Drop 实现。
  • @Shepmaster 哎呀,对不起,我把&amp;str 误读为String,因此我很担心。
【解决方案3】:

有没有办法不用box直接分配给堆?

没有。如果有,则不需要更改语言。

人们倾向于通过间接使用不稳定的语法来避免这种情况,例如使用标准容器之一,而后者又在内部使用它。

另见:

【讨论】:

    【解决方案4】:

    我最近遇到了同样的问题。根据这里和其他地方的答案,我写了一个简单的堆分配函数:

    pub fn unsafe_allocate<T>() -> Box<T> {
        let mut grid_box: Box<T>;
        unsafe {
            use std::alloc::{alloc, dealloc, Layout};
            let layout = Layout::new::<T>();
            let ptr = alloc(layout) as *mut T;
            grid_box = Box::from_raw(ptr);
        }
        return grid_box;
    }
    

    这将在内存中创建一个在T 之后自动调整大小的区域,并且不安全地使Rust 相信该内存区域是一个实际的T 值。内存可能包含任意数据;你不应该假设所有的值都是 0。

    使用示例:

    let mut triangles: Box<[Triangle; 100000]> = unsafe_allocate::<[Triangle; 100000]>();
    

    【讨论】:

    • 此代码非惯用语并且不正确地使用unsafe 代码。这可以简单地用于引入未定义的行为。
    • 但这是唯一的方法?所有其他答案都说使用alloc 是要走的路,这似乎是使用alloc 的最安全方式
    猜你喜欢
    • 2021-05-31
    • 2014-08-12
    • 2011-08-04
    • 2017-05-27
    • 1970-01-01
    • 1970-01-01
    • 2017-04-19
    • 2010-09-17
    • 2013-10-20
    相关资源
    最近更新 更多