C alloca函数 - 当尝试分配太多内存时会发生什么

时间:2014-03-11 23:17:46

标签: c alloca

在C中,alloca()函数在alloca()的调用者的堆栈帧上分配内存。

当您尝试分配无法分配的大量字节时会发生什么?

  • 在堆栈遇到堆段之前,它是否分配尽可能多的字节数?
  • 或者它什么都没有分配?

    alloca(100000000000000000000);

手册提到:

  

alloca()函数返回指向开头的指针   分配空间。如果分配导致堆栈溢出,则编程   行为未定义。

我理解这种行为是未定义的。但必须有更多话要说:

  • 它返回什么,指向main之前的堆栈顶部之后的第一个字节的指针?
  • 返回alloca()之后的堆栈指针是否与调用alloca()之前的不同?

有没有人有更多关于此的信息?

4 个答案:

答案 0 :(得分:3)

发生什么取决于您的编译器和使用的加固选项;通常情况下,您没有迹象表明它失败了,您在调用alloca后不久就会破坏随机无关内存或崩溃。通过一些加固选项,您可以使崩溃可靠,但您永远无法检测并从故障中恢复。不应该使用alloca。这是一个糟糕的错误,看起来好得令人难以置信,因为它是。

答案 1 :(得分:2)

alloca()的{​​{3}}实现读取:

# 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的函数返回时,堆栈将恢复到前一帧,并且它将再次可用。

答案 2 :(得分:1)

严格来说,没有人知道,因为“未定义的行为”本身并未定义。 (例如,alloca不是由C或POSIX标准定义的。)

仅供说明,C对“未定义行为”的定义是(ISO 9899:1999,第3.4.3节):

“使用不可移植或错误的程序结构或错误数据的行为,本国际标准没有要求

“注意可能的未定义行为包括完全忽略不可预测结果的情况,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的文件化方式行事,终止翻译或执行(发出诊断信息)。“

所以:绝对可能发生任何事情。您的硬盘可能会被重新格式化。天空可能会落入。(好吧,可能不会,但是根据你的输入,它是完全可以接受的。)你不能做出任何假设或陈述。

如果您的程序在alloca引起的堆栈溢出后对程序行为做出任何此类假设,那么您的程序就会被破坏。最好不要推测特定编译器在这种情况下可能会做什么。你的节目坏了,故事的结尾。

答案 3 :(得分:0)

在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;

}