C alloca 函数 - 当试图分配太多内存时会发生啥

Posted

技术标签:

【中文标题】C alloca 函数 - 当试图分配太多内存时会发生啥【英文标题】:C alloca function - what happens when too much memory is tried to be allocatedC alloca 函数 - 当试图分配太多内存时会发生什么 【发布时间】:2014-04-15 19:59:37 【问题描述】:

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

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

在堆栈遇到堆段之前,它是否会分配尽可能多的字节?

还是什么都不分配?

alloca(100000000000000000000);

手册中提到:

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

我了解该行为未定义。但肯定还有更多要说的:

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

有没有人知道更多这方面的信息?

【问题讨论】:

行为未定义。为什么还要说什么? 我明白手册没有更多要说的了。但可能是人们碰巧更了解在这种或那种情况下大多数系统通常会发生什么。 【参考方案1】:

会发生什么取决于您的编译器和使用的强化选项;通常,在调用alloca 后不久,您没有任何迹象表明它失败了,并且您要么破坏随机不相关的内存,要么崩溃。通过一些强化选项,您可能能够使崩溃可靠,但您永远无法检测到故障并从故障中恢复。 alloca 根本不应该被使用。这是一个糟糕的错误,看起来好得令人难以置信,因为它确实如此。

【讨论】:

alloca 使用起来很棘手,你只能希望它会彻底崩溃。你提到的另一种情况要可怕几个数量级。 @cnicutar: char local, *evil = alloca(&local - &global);【参考方案2】:

alloca() 的 GNU libc 实现如下:

# 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的函数返回时,栈会恢复到前一帧,可以再次使用。

【讨论】:

【参考方案3】:

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

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

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

“注意可能的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的记录方式表现(有或没有发出诊断消息),到终止翻译或执行(发出诊断消息)。”

所以:绝对任何事情都可能发生。您的硬盘可能会被重新格式化。天空可能会塌陷。(好吧,可能不会,但考虑到您的输入,这将是完全可以接受的。)您不能做出任何假设或陈述。

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

【讨论】:

【参考方案4】:

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


【讨论】:

以上是关于C alloca 函数 - 当试图分配太多内存时会发生啥的主要内容,如果未能解决你的问题,请参考以下文章

C:malloc/calloc/realloc/alloca内存分配函数

试图了解 x86 上 alloca() 函数的汇编实现

当试图释放堆管理器分配的内存时会发生啥,它分配的比要求的多?

C中的alloca函数

alloca() 可以替代 C++ 中的自动指针吗?

何时使用 alloca 为类成员分配内存?