Функция C alloca — что происходит, когда пытаются выделить слишком много памяти

В C функция alloca() выделяет память в кадре стека вызывающей функции alloca().

Что происходит, когда вы пытаетесь выделить огромное количество байтов, которые он никак не может выделить?

  • Выделяет ли он столько байтов, сколько может, пока стек не встретится с сегментом кучи?
  • Или вообще ничего не выделяет?

    alloca( 100000000000000000000 );

В руководстве упоминаются:

Функция alloca() возвращает указатель на начало выделенного пространства. Если выделение вызывает переполнение стека, поведение программы не определено.

Я понимаю, что поведение не определено. Но должно быть больше, чтобы сказать, чем это:

  • Что он возвращает, указатель на первый байт после вершины стека перед вызовом main?
  • После возврата alloca() указатель стека отличается от того, что было до вызова alloca()?

У кого-нибудь есть дополнительная информация об этом?


person Stefan    schedule 11.03.2014    source источник
comment
Поведение не определено. Почему должно быть что-то еще, чтобы сказать?   -  person Keith Thompson    schedule 12.03.2014
comment
Я понимаю, что в руководстве больше нечего сказать. Но может случиться так, что люди больше знают о том, что обычно происходит в большинстве систем в той или иной ситуации.   -  person Stefan    schedule 12.03.2014


Ответы (4)


Реализация GNU libc alloca() читает:

# define alloca(size)   __builtin_alloca (size)

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


Рассмотрим конкретный случай. На моей машине это сборка для alloca(100000000000L):

0e9b:  movabsq $-100000000016, %rax ; * Loads (size + 16) into rax.
0ea5:  addq   %rax, %rsp            ; * Adds it to the top of the stack.
0ea8:  movq   %rsp, -48(%rbp)       ; * Temporarily stores it.
0eac:  movq   -48(%rbp), %rax       ; * These five instructions round the
0eb0:  addq   $15, %rax             ;   value stored to the next multiple
0eb4:  shrq   $4, %rax              ;   of 0x10 by doing:
0eb8:  shlq   $4, %rax              ;   rax = ((rax+15) >> 4) << 4
0ebc:  movq   %rax, -48(%rbp)       ;   and storing it again in the stack.
0ec0:  movq   -48(%rbp), %rax       ; * Reads the rounded value and copies
0ec4:  movq   %rax, -24(%rbp)       ;   it on the previous stack position.

Скомпилировано с использованием gcc-4.2 -g test.c -o test из следующей программы:

Теперь, когда есть на что ссылаться, можно ответить на ваши вопросы:

Выделяет ли он столько байтов, сколько может, пока стек не встретится с сегментом кучи?

Он просто слепо увеличивает стек на запрошенное количество байтов. Проверка границ вообще не выполняется, поэтому и указатель стека, и возвращаемое значение теперь, вероятно, будут находиться в недопустимом месте. Попытка чтения/записи из возвращаемого значения (или помещения в стек) приведет к ошибке SIGSEGV.

Что он возвращает, указатель на первый байт после вершины стека перед вызовом main?

Он возвращает указатель на первый байт выделенной памяти.

После возврата alloca() указатель стека отличается от того, что был до вызова alloca()?

Да, см. объяснение выше. Кроме того, когда функция, вызвавшая alloca, вернется, стек будет восстановлен до предыдущего кадра, и его снова можно будет использовать.

person MBlanc    schedule 12.03.2014

Что произойдет, зависит от вашего компилятора и используемых опций усиления; как правило, у вас нет признаков того, что это не удалось, и вы либо затираете случайную не связанную память, либо вылетаете вскоре после вызова alloca. С некоторыми вариантами усиления защиты вы можете сделать сбой надежным, но вы никогда не сможете обнаружить сбой и восстановиться после него. alloca просто не следует использовать. Это была грубая ошибка, которая выглядела слишком хорошо, чтобы быть правдой, потому что так оно и есть.

person R.. GitHub STOP HELPING ICE    schedule 11.03.2014
comment
alloca сложно в использовании, и вы можете только надеяться, что он выйдет из строя чисто. Другой сценарий, о котором вы упомянули, на несколько порядков страшнее. - person cnicutar; 12.03.2014
comment
@cnicutar: char local, *evil = alloca(&local - &global); - person R.. GitHub STOP HELPING ICE; 12.03.2014

Строго говоря, никто не знает, потому что «неопределенное поведение» само по себе не определено. (например, alloca не определяется стандартами C или POSIX).

Только для иллюстрации, определение C «неопределенного поведения» (ISO 9899:1999, раздел 3.4.3):

«поведение при использовании непереносимой или ошибочной программной конструкции или ошибочных данных, для которых настоящий стандарт не устанавливает требований

"ПРИМЕЧАНИЕ Возможное неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами до поведения во время трансляции или выполнения программы документированным образом, характерным для среды (с выдачей или без выдачи диагностического сообщения), до прекращения трансляции или выполнения (с выдача диагностического сообщения)".

Итак: абсолютно все может случиться. Ваш жесткий диск может быть переформатирован. Небо может упасть. (Хорошо, вероятно, нет, но это было бы вполне приемлемо, учитывая ваш вклад.) Вы не можете делать никаких предположений или утверждений.

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

person user3392484    schedule 11.03.2014

В Windows вы можете восстановить его. Протестировано с помощью gcc:

/*
 * Show how get memory from stack without crash
 * Currently, compiles ok with mingw, and the latest version of tiny c (from git)
 * Last Version: 29/june/2014
 * Programmed by Carlos Montiers
 */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <malloc.h>
#include <setjmp.h>

int _resetstkoflw(void);

static jmp_buf alloca_jmp_buf;

LONG WINAPI AllocaExceptionFilter(EXCEPTION_POINTERS * ExceptionInfo)
{

    switch (ExceptionInfo->ExceptionRecord->ExceptionCode) {
    case STATUS_STACK_OVERFLOW:

    // reset the stack
    if (0 == _resetstkoflw()) {
        printf("Could not reset the stack!\n");
        _exit(1);
    }

    longjmp(alloca_jmp_buf, 1);

    break;
    }

    return EXCEPTION_EXECUTE_HANDLER;
}

int main()
{
    void *m;
    int alloca_jmp_res;
    LPTOP_LEVEL_EXCEPTION_FILTER prev;

    //replace the exception filter function saving the previous
    prev = SetUnhandledExceptionFilter(AllocaExceptionFilter);

    alloca_jmp_res = setjmp(alloca_jmp_buf);
    if ((0 == alloca_jmp_res)) {
    m = alloca(INT_MAX);

    } else if ((1 == alloca_jmp_res)) {
    m = NULL;

    }
    //restore exception filter function
    SetUnhandledExceptionFilter(prev);

    if (!m) {
    printf("alloca Failed\n");
    }

    printf("Bye\n");
    return 1;

}
person carlos    schedule 07.07.2014