在什么时候通常为C++中的局部变量分配内存?

sha*_*oth 26 c++ stack-overflow memory-management local-variables visual-c++

我正在调试一个相当奇怪的堆栈溢出,据说是在堆栈上分配太大的变量引起的,我想澄清以下内容.

假设我有以下功能:

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 ) );
    }
 }
Run Code Online (Sandbox Code Playgroud)

我理解,它依赖于编译器,也取决于优化器决定什么,但为这些局部变量分配内存的典型策略是什么?

输入功能后是否会立即分配最坏情况(1 + 512千字节)或首先分配1千字节,然后根据条件另外分配1或512千字节?

jal*_*alf 15

在许多平台/ ABI上,当您输入函数时,将分配整个堆栈帧(包括每个局部变量的内存).在其他情况下,根据需要,逐位推送/弹出存储器是很常见的.

当然,在一次性分配整个堆栈帧的情况下,不同的编译器可能仍然决定不同的堆栈帧大小.在您的情况下,一些编译器会错过优化机会,并为每个局部变量分配唯一的内存,甚至是代码的不同分支(在1 * 1024数组和512 * 1024您的情况下都是一个)中的那些,其中更好的优化编译器应该只分配通过函数的任何路径所需的最大内存(else在你的情况下的路径,所以分配一个512kb的块应该是足够的).如果您想知道您的平台的功能,请查看反汇编.

但是看到整个内存块立即被分配并不会让我感到惊讶.

  • 我可以至少在异常的情况下确认Visual Studio,它为您输入时可能从函数抛出的所有异常分配存储空间.递归这样的函数对于你的堆栈来说是地狱.尝试一下 - 制作一个递归函数,在第4000次递归中抛出一个2k对象 - 它会因堆栈溢出而崩溃. (2认同)

Mat*_* M. 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

产量:

; 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*)
Run Code Online (Sandbox Code Playgroud)

您可以alloca在功能顶部看到3 .

我必须承认我有点失望b2并且b3没有在IR中折叠,因为只会使用其中一个.

  • 哇,这是相当令人失望的 - 它比我预期的最糟糕的情况更糟糕. (2认同)
  • 我刚刚在Visual C++ 10上进行了测试 - 行为与您在LLVM上看到的相同.很伤心.我不能指望分配算法那么糟糕. (2认同)

小智 10

此优化称为"堆栈着色",因为您将多个堆栈对象分配给同一地址.这是我们知道LLVM可以改进的领域.目前,LLVM仅对寄存器分配器为溢出槽创建的堆栈对象执行此操作.我们还想扩展它来处理用户堆栈变量,但我们需要一种方法来捕获IR中值的生命周期.

我们计划在这里做一个粗略的草图:http: //nondot.org/sabre/LLVMNotes/MemoryUseMarkers.txt

关于此的实施工作正在进行中,主要部分实施了几个部分.

-克里斯


ten*_*our 4

您的本地(堆栈)变量分配在与堆栈帧相同的空间中。当函数被调用时,堆栈指针会发生变化,为堆栈帧“腾出空间”。它通常在一次调用中完成。如果您使用局部变量使用堆栈,则会遇到堆栈溢出。

无论如何,~512 kbytes 对于堆栈来说确实太大了;您应该使用在堆上分配它std::vector