在C语言中,alloca()函数在alloca()调用者的堆栈帧上分配内存。
当你尝试分配大量它不可能分配的字节时,会发生什么?
- 它是否分配尽可能多的字节,直到堆栈满足堆段?
-
或者它什么都不分配?
手册提到:
alloca()函数返回指向对象开头的指针分配空间。如果分配导致堆栈溢出,则执行程序行为未定义
我知道行为是未定义的。但肯定有更多的话要说:
- 它返回什么,在调用main之前指向堆栈顶部后的第一个字节的指针?
- alloca()返回堆栈指针是不同的然后它是什么之前alloca()被调用?
有人有更多的信息吗?
结果取决于你的编译器和使用的加固选项;通常情况下,没有任何迹象表明它失败了,在调用alloca
后不久,您要么随机地清除不相关的内存,要么崩溃。使用一些强化选项,您可能能够使崩溃可靠,但您永远无法检测到故障并从故障中恢复。根本不应该使用alloca
。这是一个糟糕的错误,看起来好得令人难以置信,因为它是。
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
的函数返回时,堆栈将恢复到前一帧,并且它将再次可用。
严格来说,没有人知道,因为"未定义行为"本身并没有定义。(例如,alloca不是由C或POSIX标准定义的)。
仅作为说明,C语言对"未定义行为"的定义是(ISO 9899:1999, section 3.4.3):
"在使用不可移植的或错误的程序结构或错误的数据时的行为,本国际标准对此没有要求
"注:可能的未定义行为范围从完全忽略具有不可预测结果的情况,到在翻译或程序执行期间以环境特征的文档方式行为(有或没有发出诊断消息),到终止翻译或执行(发出诊断消息)。"
所以:绝对任何事情都有可能发生。您的硬盘可能会被重新格式化。天可能塌下来。(好吧,可能不是,但考虑到你的输入,这是完全可以接受的。)你不能做任何假设或陈述。
如果你的程序在分配引起的堆栈溢出后对程序行为做出任何这样的假设(或依赖),那么你的程序就坏了。最好不要猜测特定编译器在这种情况下会做什么。你的程序坏了,就这样。
在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 Failedn");
}
printf("Byen");
return 1;
}