2

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

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

  • 在堆栈遇到堆段之前,它是否会分配尽可能多的字节?
  • 或者它根本没有分配任何东西?

    分配(100000000000000000000);

手册中提到:

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

我知道这种行为是未定义的。但必须说的远不止这些:

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

有没有人有更多关于这个的信息?

4

4 回答 4

3

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

于 2014-03-11T23:25:38.247 回答
2

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

于 2014-03-12T01:18:56.157 回答
1

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

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

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

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

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

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

于 2014-03-11T23:26:31.347 回答
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;

}
于 2014-07-07T15:10:25.880 回答