我正在调试一个相当奇怪的堆栈溢出,据说是由于在堆栈上分配了太大的变量而引起的,我想澄清以下内容。
假设我有以下功能:
void function()
{
char buffer[1 * 1024];
if( condition ) {
char buffer[1 * 1024];
doSomething( buffer, sizeof( buffer ) );
} else {
char buffer[512 * 1024];
doSomething( buffer, sizeof( buffer ) );
}
}
我理解,它依赖于编译器,也取决于优化器决定什么,但 典型策略 用于为这些局部变量分配内存是什么?
一旦输入函数会立即分配最坏情况(1 + 512千字节)还是首先分配1千字节,然后根据条件另外分配1或512千字节?
答案 0 :(得分:15)
在许多平台/ ABI上,当您输入函数时,将分配整个堆栈帧(包括每个局部变量的内存)。在其他情况下,通常需要逐位推送/弹出存储器。
当然,在一次性分配整个堆栈帧的情况下,不同的编译器可能仍然决定不同的堆栈帧大小。在您的情况下,一些编译器会错过优化机会,并为每个局部变量分配唯一内存,甚至是代码的不同分支中的那些(1 * 1024
数组和512 * 1024
在你的情况下一个),其中一个更好的优化编译器应该只分配通过函数的任何路径所需的最大内存(在你的情况下为else
路径,所以分配一个512kb块就足够了) 。
如果您想知道您的平台的功能,请查看反汇编。
但是看到立即分配的整个内存块并不令我感到惊讶。
答案 1 :(得分:11)
我查看了LLVM:
void doSomething(char*,char*);
void function(bool b)
{
char b1[1 * 1024];
if( b ) {
char b2[1 * 1024];
doSomething(b1, b2);
} else {
char b3[512 * 1024];
doSomething(b1, b3);
}
}
收率:
; ModuleID = '/tmp/webcompile/_28066_0.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-unknown-linux-gnu"
define void @_Z8functionb(i1 zeroext %b) {
entry:
%b1 = alloca [1024 x i8], align 1 ; <[1024 x i8]*> [#uses=1]
%b2 = alloca [1024 x i8], align 1 ; <[1024 x i8]*> [#uses=1]
%b3 = alloca [524288 x i8], align 1 ; <[524288 x i8]*> [#uses=1]
%arraydecay = getelementptr inbounds [1024 x i8]* %b1, i64 0, i64 0 ; <i8*> [#uses=2]
br i1 %b, label %if.then, label %if.else
if.then: ; preds = %entry
%arraydecay2 = getelementptr inbounds [1024 x i8]* %b2, i64 0, i64 0 ; <i8*> [#uses=1]
call void @_Z11doSomethingPcS_(i8* %arraydecay, i8* %arraydecay2)
ret void
if.else: ; preds = %entry
%arraydecay6 = getelementptr inbounds [524288 x i8]* %b3, i64 0, i64 0 ; <i8*> [#uses=1]
call void @_Z11doSomethingPcS_(i8* %arraydecay, i8* %arraydecay6)
ret void
}
declare void @_Z11doSomethingPcS_(i8*, i8*)
您可以在功能顶部看到3 alloca
。
我必须承认,b2
和b3
在IR中没有合并,我感到有些失望,因为只会使用其中一个。
答案 2 :(得分:10)
此优化称为“堆栈着色”,因为您将多个堆栈对象分配到同一地址。这是我们知道LLVM可以改进的领域。目前,LLVM仅对寄存器分配器为溢出槽创建的堆栈对象执行此操作。我们也想扩展它来处理用户堆栈变量,但是我们需要一种方法来捕获IR中值的生命周期。
我们计划在这里做一个粗略的草图: http://nondot.org/sabre/LLVMNotes/MemoryUseMarkers.txt
有关这方面的实施工作正在进行中,主要内容已经实施了几项。
-Chris
答案 3 :(得分:4)
您的本地(堆栈)变量与堆栈帧分配在同一空间中。调用该函数时,堆栈指针将更改为堆栈框架的“腾出空间”。它通常在一次通话中完成。如果使用带有局部变量的堆栈,则会遇到堆栈溢出。
在任何情况下, ~512千字节对于堆栈实在太大了;你应该使用std::vector
在堆上分配它。
答案 4 :(得分:0)
正如您所说,它依赖于编译器,但您可以考虑使用alloca来克服此问题。变量仍将在堆栈上分配,并且在超出范围时仍会自动释放,但您可以控制何时以及是否分配了堆栈空间。
虽然use of alloca is typically discouraged,但它确实在上述情况下有用。