是否可以在 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<T>; 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 运行时确定大小的堆栈分配数组?的主要内容,如果未能解决你的问题,请参考以下文章