为什么'alloca'不检查它是否可以分配内存?
为什么为什么'alloca'不检查它是否可以分配内存?,c,memory-management,stack-overflow,sbrk,alloca,C,Memory Management,Stack Overflow,Sbrk,Alloca,为什么alloca不检查它是否可以分配内存 void *safe_alloca(size_t size) { if(alloca(0) - sbrk(0) < size) { errno = ENOMEM; return (void *)-1; } else { return alloca(size); } } 从man 3 alloca: 如果分配导致堆栈溢出,则程序行为未定义…如果无法扩展堆栈帧,则没有错误指示
alloca
不检查它是否可以分配内存
void *safe_alloca(size_t size)
{
if(alloca(0) - sbrk(0) < size) {
errno = ENOMEM;
return (void *)-1;
} else {
return alloca(size);
}
}
从man 3 alloca
:
如果分配导致堆栈溢出,则程序行为未定义…如果无法扩展堆栈帧,则没有错误指示
为什么alloca
不/不能检查它是否可以分配更多内存
按照我的理解,alloca
在堆栈上分配内存,而(s)brk
在堆上分配内存。发件人:
堆区域由malloc、calloc、realloc和free管理,它们可以使用brk和sbrk系统调用来调整其大小
从man 3 alloca
:
函数的作用是:在调用方的堆栈帧中分配大小字节的空间
堆栈和堆在汇聚的方向上增长,如维基百科图所示:
(上图由Dougct根据发布)
现在alloca
和(s)brk
都返回一个指向新分配内存开头的指针,这意味着它们都必须知道堆栈/堆在当前时刻的结束位置。事实上,从MAN2SBRK
:
以0为增量调用sbrk()可用于查找程序中断的当前位置
因此,按照我的理解,检查alloca
是否可以分配所需的内存本质上归结为检查堆栈的当前端和堆的当前端之间是否有足够的空间。如果在堆栈上分配所需的内存会使堆栈到达堆,则分配失败;否则,它就会成功
那么,为什么不能用这样的代码来检查alloca
是否可以分配内存呢
void *safe_alloca(size_t size)
{
if(alloca(0) - sbrk(0) < size) {
errno = ENOMEM;
return (void *)-1;
} else {
return alloca(size);
}
}
void*safe\u alloca(大小)
{
if(alloca(0)-sbrk(0)<尺寸){
errno=ENOMEM;
返回(无效*)-1;
}否则{
返回alloca(大小);
}
}
这对我来说更让人困惑,因为显然(s)brk
可以做这样的检查。从man 2 sbrk
:
brk()将数据段的结尾设置为addr指定的值,当该值合理时,系统有足够的内存,并且进程不会超过其最大数据大小(请参阅setrlimit(2))
因此,如果
(s)brk
可以进行这样的检查,那么为什么不能alloca
?情况有点过时:在现代系统中,堆内存区域和包含调用堆栈的内存区域是完全独立的实体,在64位系统中它们相距甚远。内存中断的概念是为具有小地址空间的系统设计的
因此,堆栈空间的限制不是它可能会增长到堆的顶部,而是内核可能没有任何内存来支持它。或者内核可能会认为堆栈增长过多(达到了某些限制),从而导致进程崩溃
您的进程只需通过减少堆栈指针并在其中存储一些数据来增加堆栈。如果该内存访问当前未映射到您的地址空间,则硬件会立即向操作系统内核发送此情况的信号,操作系统内核会检查发生内存访问失败的位置,如果该位置正好位于堆栈内存区域下方,则会立即扩展该内存映射,在该位置映射新的内存页,并将控制权交还给进程以重试其内存访问该过程没有看到任何此类情况。它只是看到它对堆栈内存的访问成功了
alloca()
在任何方面都不会偏离这一点:您要求它在堆栈上分配一些内存,它通过相应地减少堆栈指针来实现这一点。但是,如果您的分配非常大,操作系统不会将对它的内存访问视为有效的堆栈内存访问,那么它(很可能,也很有希望)会以一个错误终止您的进程。但是,由于行为是未定义的,任何事情都有可能发生。alloca
是一种非标准的编译器,其卖点是它编译为非常轻量级的代码,甚至可能是一个。它基本上执行在每个函数开始时使用局部变量执行的操作-按指定的量移动堆栈指针寄存器并返回新值。与sbrk
不同,alloca
完全在用户空间中,无法知道还有多少堆栈可用
堆栈向堆方向增长的图像是学习内存管理基础知识的有用心智模型,但在现代系统上并不准确:
- 正如cmaster在他的回答中所解释的,堆栈大小主要受内核强制的限制,而不是堆栈与堆的碰撞,特别是在64位系统上
- 在多线程进程中,没有一个堆栈,而是每个线程有一个堆栈,它们显然不能全部向堆方向增长
- 堆本身不是连续的。现代
实现使用多个竞技场,并将大量分配转移到匿名malloc
,确保mmap
。后者也在传统描述的单一竞技场“堆”之外免费
可以想象一个版本的
alloca
从操作系统查询此信息并返回正确的错误条件,但是它的性能优势将丢失,甚至可能与malloc
(这只是偶尔需要转到操作系统为进程获取更多内存,并且通常在用户空间中工作).我认为alloca
基本上是模仿调用函数中在堆栈上分配局部变量时所做的操作。请注意,C99增加了对可变大小数组的支持,使得alloca
不那么重要。此外,请注意,仅仅因为地址空间中有空间,它就不会