为什么`alloca`不检查它是否可以分配内存?
为什么alloca
不检查它是否可以分配内存?
从man 3 alloca
:
如果分配导致堆栈溢出,则程序行为未定义。 ...如果堆栈帧无法扩展,则没有错误指示。
为什么alloca
不能/不能检查它是否可以分配更多内存?
我理解它的方式alloca
在堆栈上分配内存,而(s)brk
在堆上分配内存。 从https://en.wikipedia.org/wiki/Data_segment#Heap:
堆区由malloc,calloc,realloc和free管理,可以使用brk和sbrk系统调用来调整其大小
从man 3 alloca
:
alloca()函数在调用者的堆栈帧中分配空间的大小字节。
堆栈和堆栈正在朝着趋同的方向发展,如此维基百科图表所示:
(以上图片来源于CC BY-SA 3.0发布的Dougct Wikimedia Commons)
现在alloca
和(s)brk
返回一个指向新分配内存的开始的指针,这意味着它们必须知道堆栈/堆在当前时刻结束于何处。 事实上,从man 2 sbrk
:
以增量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);
}
}
这对我来说更令人困惑,因为显然(s)brk
可以做这样的检查。 从man 2 sbrk
:
brk()将数据段的末尾设置为由addr指定的值,当该值合理时,系统具有足够的内存,并且该进程不超过其最大数据大小(请参阅setrlimit(2))。
所以如果(s)brk
可以做这样的检查,那为什么不能alloca
?
alloca
是一个非标准的编译器内部函数,它的卖点是它编译为非常轻量级的代码,甚至可能是单个指令。 它基本上是在每个函数的开始处使用局部变量执行操作 - 将堆栈指针寄存器移动指定的数量并返回新值。 与sbrk
不同, alloca
完全在用户空间中,无法知道剩下多少堆栈可用。
堆栈向堆栈方向发展的形象是学习存储器管理基础知识的有用心智模型,但它在现代系统中并不准确:
malloc
实现使用多个场所来提高并发性能,并将大量分配卸载到匿名mmap
,从而确保将free
分配给操作系统。 后者也在传统描绘的单一竞技场“堆”之外。 可以想象一个从操作系统查询这些信息的alloca
版本,并返回一个适当的错误条件,但是它的性能优势将会丢失,甚至可能与malloc
相比(这只是偶尔需要去操作系统去抓取该过程的内存更多,并且通常在用户空间中工作)。
图片有点过时了:在现代系统中,堆内存区域和包含调用堆栈的内存区域是完全独立的实体,它们在64位系统上相距很远。 内存中断的概念是为具有小地址空间的系统而设计的。
因此,堆栈空间的限制并不是它可能会成长到堆栈顶部,限制是内核可能没有任何剩余内存来支持它。 或者内核可能会决定你的堆栈增长太多(达到一定的限制),从而降低你的进程。
你的过程通过递减堆栈指针,并在那里存储一些数据来增长堆栈。 如果该存储器访问当前未映射到您的地址空间,则硬件会立即将此情况通知给操作系统内核,操作系统内核会检查发生故障存储器访问的位置,如果该位置恰好位于堆栈内存区域之下,则会立即扩展该内存映射,在那里映射新的内存页面,并将控制权交还给您的进程以重试其内存访问。 该过程没有看到任何这一点 。 它只是看到它对堆栈内存的访问成功了。
alloca()
不会以任何方式偏离它:你要求它在堆栈上分配一些内存,并且通过相应地减少堆栈指针来实现。 但是,如果您的分配量太大以致操作系统没有看到内存访问为有效的堆栈内存访问,它将(有可能并希望)通过SEGFAULT来终止您的进程。 但是,由于行为未定义,因此可能会发生任何事情。
上一篇: Why does `alloca` not check if it can allocate memory?
下一篇: What are the contents of the memory just allocated by `malloc()`?