Подтвердить что ты не робот

В какой момент обычно выделяется память для локальных переменных на С++?

Я отлаживаю довольно странное переполнение стека, предположительно вызванное распределением слишком больших переменных в стеке, и я хотел бы пояснить следующее.

Предположим, что у меня есть следующая функция:

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 килобайт?

4b9b3361

Ответ 1

Ваши локальные (стек) переменные распределяются в том же пространстве, что и стек кадров. Когда функция вызывается, указатель стека изменяется на "сделать комнату" для фрейма стека. Обычно это выполняется за один раз. Если вы потребляете стек с локальными переменными, вы столкнетесь с переполнением стека.

~ 512 кбайт действительно слишком велико для стека в любом случае; вы должны выделить это в куче, используя std::vector.

Ответ 2

На многих платформах /ABI весь стек (включая память для каждой локальной переменной) выделяется при вводе функции. В других случаях, как правило, он по-разному используется для разгона/поп-памяти.

Конечно, в случаях, когда весь стековый фрейм распределяется за один раз, разные компиляторы могут по-прежнему решать разные размеры кадров стека. В вашем случае некоторые компиляторы упускают возможность оптимизации и выделяют уникальную память для каждой локальной переменной, даже те, которые находятся в разных ветвях кода (как массив 1 * 1024, так и 512 * 1024 один в вашем случае), где лучший оптимизирующий компилятор должен выделять максимальную память, необходимую для любого пути через функцию (путь else в вашем случае, поэтому выделение блока 512 КБ должно быть достаточным).  Если вы хотите знать, что делает ваша платформа, посмотрите на разборку.

Но меня не удивило бы, чтобы сразу сразу выделил весь фрагмент памяти.

Ответ 3

Я проверил 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, поскольку только один из них когда-либо будет использоваться.

Ответ 4

Эта оптимизация известна как "раскраска стека", потому что вы назначаете несколько объектов стека на один и тот же адрес. Это область, в которой мы знаем, что LLVM может улучшиться. В настоящее время LLVM делает это только для объектов стека, созданных распределителем регистров для интервалов разлива. Мы хотели бы расширить это, чтобы обрабатывать переменные пользовательского стека, но нам нужен способ захватить время жизни значения в IR.

Есть приблизительный эскиз того, как мы планируем сделать это здесь: http://nondot.org/sabre/LLVMNotes/MemoryUseMarkers.txt

Выполнение этой работы продолжается, в mainline реализовано несколько частей.

-Крис

Ответ 5

Как вы говорите, он зависит от компилятора, но вы можете рассмотреть возможность использования alloca для преодоления этого. Переменные все равно будут выделены в стеке и все еще автоматически освобождаются по мере выхода из области видимости, но вы берете контроль над тем, когда и если выделено пространство стека.

В то время как использование alloca обычно обескураживается, оно имеет свои применения в таких ситуациях, как указано выше.