Rust 是否在堆栈上分配以使用 Some 初始化 Option 引用?

问题描述

我一直在受限的嵌入式环境中使用 Rust(在 STM32F303 MCU 上),我注意到我的一些函数分配了出乎意料的大量堆栈空间。 在这种环境下,我没有分配器,需要在堆栈上分配大的、可变的、静态的数据结构。 最终,我发现一些函数意外地在堆栈上分配了空间,并导致我的内存受限堆栈溢出。

我希望了解需要在堆栈上为以下 mutate 函数分配多少内存。 我一直在这个网站上寻找这个问题的答案,但似乎没有人为这个更普遍的问题提供答案。 我知道对于下面的这个代码块,我可以查看 LLVM/程序集以查看分配的位置,但我试图了解如何预测何时为一般类问题进行堆栈分配 - 独立于编译器选项和优化器。 以下问题是一个玩具示例,它模仿了我在嵌入式 Rust 程序中使用(并遇到问题)的模式。

问题:下面的mutate需要在栈上分配多少内存?

struct Parent {
    data: Option<[u32; 1024]>,}

impl Parent {
    pub fn new() -> Self {
        Parent { data: None }
    }

    // Ideally,this function should allocate negligible memory on the stack
    pub fn mutate(&mut self) {
        let arr = [0u32; 1024];
        self.data = Some(arr);
        for i in 0..self.data.unwrap().len() {
            self.data.unwrap()[i] = i as u32;
        }
    }
}

fn main() {
    let mut it = Parent::new();
    it.mutate();
}

注意事项

  • 其他帖子似乎表明这种行为取决于优化器。如果是这种情况,有没有办法重写上面的代码,以便每个函数的堆栈分配大小对代码的读者来说是显而易见的?
  • let 关键字(mutate 函数中的第一行)对是否在堆栈上分配大数组有任何影响吗?
  • 我确信我可以使用 unsafe rust 来确保此函数不需要进行任何分配/memcpys(就像我在 C 中所做的那样)。在不使用 unsafe 的情况下,有没有更好的方法来做到这一点?

预先感谢您对此问题的任何帮助。

解决方法

问题:下面的mutate需要在栈上分配多少内存?

4kB (32 = 8 * 1024)?您实际上是在堆栈上创建了一个本地数组,而 Rust 没有放置 new,因此即使您没有在堆栈上创建一个本地数组,它基本上也取决于它处理分配的优化器。

另请注意,您的 Parent 结构也必须始终占用 4kB,实际上它可能需要 4kB + 4 或 8 个字节(不确定 u32 数组的对齐要求是什么)作为 Option 的标签该数组没有无效值,rustc 可将其用于细分变体优化。

编辑:哦等等,您还使用 self.data.unwrap() 调用将数组 back 复制到函数中,所以还有 2 个,所以至少 12kB。事实上,将其插入 Compiler Explorer 它告诉我:

example::Parent::mutate: mov eax,28856

所以这是 28k,不太确定额外的来自哪里。

无论如何激活-O,看起来都被优化了,所以......幸运吗?:

example::Parent::mutate:
        push    rax
        mov     dword ptr [rdi],1
        add     rdi,4
        mov     edx,4096
        xor     esi,esi
        call    qword ptr [rip + memset@GOTPCREL]
        pop     rax
        ret