是否可以在 Rust 运行时确定大小的堆栈分配数组?

Posted

技术标签:

【中文标题】是否可以在 Rust 运行时确定大小的堆栈分配数组?【英文标题】:Is it possible to have stack allocated arrays with the size determined at runtime in Rust? 【发布时间】:2015-01-09 11:43:39 【问题描述】:

是否有与 alloca 等效的方法在 Rust 中创建可变长度数组?

我正在寻找以下 C99 代码的等效项:

void go(int n) 
    int array[n];
    // ...

【问题讨论】:

你总是可以从 c 中导入 alloca 函数。如果您添加某种溢出检查,您甚至可以确保它是安全的。 @ker:这不太可能。 alloca 并不是一个真正的函数,它更像是一个编译器内在函数。至少对于 LLVM,它是一个实际的 IR 指令,因此没有直接“导入”它的方法。您需要能够编写内联 IR,而您目前还不能。 这让我想知道那些家伙导入了什么:github.com/tomaka/cpal/blob/master/alsa-sys/src/lib.rs#L2135 ...我很确定它现在是一个 c 标准函数 (man7.org/linux/man-pages/man3/alloca.3.html) 一般来说,由于大多数调用约定的工作方式,alloca 不可能成为常规函数。一个函数只能改变它自己的栈帧,不能改变它的父栈帧。手册页中的注释部分指出“函数”是编译器和机器相关的。这就是原因。 【参考方案1】:

这是不可能的直接,因为在支持它的语言中没有直接的语法。

话虽如此,C99 的这个特殊功能值得商榷,它有一定的优势(缓存局部性和绕过 malloc)但它也有缺点(容易炸毁堆栈,阻碍许多优化,可能会转向静态偏移到动态偏移,...)。

现在,我建议您改用Vec。如果您有性能问题,那么您可以研究所谓的“小向量优化”。我经常在需要性能的 C 代码中看到以下模式:

SomeType array[64] = ;
SomeType* pointer, *dynamic_pointer;
if (n <= 64) 
    pointer = array;
 else 
    pointer = dynamic_pointer = malloc(sizeof(SomeType) * n);


// ...

if (dynamic_pointer)  free(dynamic_pointer); 

现在,这是 Rust 很容易支持的东西(并且在某种程度上更好):

enum InlineVector<T, const N: usize> 
    Inline(usize, [T; N]),
    Dynamic(Vec<T>),

您可以在下面看到一个简单的实现示例。

然而,重要的是你现在有一个类型:

当需要少于 N 个元素时使用堆栈 否则移到堆中,以避免炸毁堆栈

当然,它也总是为堆栈上的N个元素保留足够的空间,即使你只使用2个;但是作为交换,您无需调用 alloca,因此您可以避免对您的变体进行动态偏移的问题。

与 C 相反,您仍然可以从生命周期跟踪中受益,这样您就不会在函数外部意外返回对堆栈分配数组的引用。

注意:在 Rust 1.51 之前,您将无法通过 N 进行自定义。


我将展示最“明显”的方法:

enum SmallVector<T, const N: usize> 
    Inline(usize, [T; N]),
    Dynamic(Vec<T>),


impl<T: Copy + Clone, const N: usize> SmallVector<T, N> 
    fn new(v: T, n: usize) -> Self 
        if n <= N 
            Self::Inline(n, [v; N])
         else 
            Self::Dynamic(vec![v; n])
        
    


impl<T, const N: usize> SmallVector<T, N> 
    fn as_slice(&self) -> &[T] 
        match self 
            Self::Inline(n, array) => &array[0..*n],
            Self::Dynamic(vec) => vec,
        
    

    fn as_mut_slice(&mut self) -> &mut [T] 
        match self 
            Self::Inline(n, array) => &mut array[0..*n],
            Self::Dynamic(vec) => vec,
        
    


use std::ops::Deref, DerefMut;

impl<T, const N: usize> Deref for SmallVector<T, N> 
    type Target = [T];

    fn deref(&self) -> &Self::Target 
        self.as_slice()
    


impl<T, const N: usize> DerefMut for SmallVector<T, N> 
    fn deref_mut(&mut self) -> &mut Self::Target 
        self.as_mut_slice()
    

用法:

fn main() 
    let mut v = SmallVector::new(1u32, 4);
    v[2] = 3;
    println!(": ", v.len(), v[2])

按预期打印4: 3

【讨论】:

@malbarbo:确实,它也解决了没有智能类型级别整数的问题!我没有想过要一个 array 类型给用户让她指定大小。 smallvec crate 提供了这个。 @wizzwizz4 部分。 Array trait 仅针对大小的子集实现,使用起来有点麻烦。总比没有好,但显然是缺少 const 泛型的解决方法。 Inline 变体的切片实现不应该使用该变体的大小作为界限吗?此处的示例代码对整个 len 64 数组进行了切片。所以切片的长度会错误。 @dubiousjim:确实,已修复。此外,如果您想尝试它而不必实际构建 64 个 T,您将需要使用 [MaybeUninit&lt;T&gt;; 64],但我暂时将其省略。最后,正在进行将内联存储传递给 rustc 的 std 结构的工作,但目前还处于原型阶段。【参考方案2】:

没有。

在 Rust 中这样做需要能够在堆栈上存储动态大小的类型 (DST),例如 [i32],这是该语言不支持的。

据我所知,更深层次的原因是 LLVM 并不真正支持这一点。我相信您可以做到这一点,但它显着干扰了优化。因此,我不知道有任何近期计划允许这样做。

【讨论】:

LLVM 首先是一个 C/C++ 优化器,因此它可能相对较好地支持它。 每次我看到它被提出时,有人说动态调整块内的堆栈大小会严重损害 LLVM 进行优化的能力。我从来没有找到任何确凿的证据来支持或反驳这一点,所以我或多或少地接受了我所听到的。虽然我喜欢在这个问题上错了:堆栈 DST 会相当漂亮。 :) Rust 现在支持 DST 作为 Rust 1.0 alpha 的一部分! blog.rust-lang.org/2015/01/09/Rust-1.0-alpha.html @MichaelTang:Rust 支持 DST 已经有一段时间了。除非我遗漏了什么,否则它支持将 DST 存储在变量中,或从函数中返回它们。 @DK:你说的很对。我很惭愧地承认我昨天花了太多时间试图让它工作,然后才意识到只有 DST 支持!= 提问者想要什么。

以上是关于是否可以在 Rust 运行时确定大小的堆栈分配数组?的主要内容,如果未能解决你的问题,请参考以下文章

Rust 堆栈大小 [重复]

将数组分配到运行时已知大小的堆上

是否在C中的堆栈或堆上创建激活记录?

有没有办法直接在堆上分配一个标准的 Rust 数组,完全跳过堆栈?

变长 std::array 像

Rust - 调试与发布模式的堆栈大小是不是不同?