问题描述
我一直在受限的嵌入式环境中使用 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