В какой момент память обычно выделяется для локальных переменных в C ++? - PullRequest
26 голосов
/ 17 августа 2011

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

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

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

Ответы [ 5 ]

15 голосов
/ 17 августа 2011

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

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

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

11 голосов
/ 17 августа 2011

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

10 голосов
/ 17 августа 2011

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

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

В настоящее время ведутся работы по внедрению, в основной строке реализовано несколько частей.

-Chris

4 голосов
/ 17 августа 2011

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

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

0 голосов
/ 17 августа 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...