为什么 `alloca` 不检查它是否可以分配内存?

Why does `alloca` not check if it can allocate memory?

为什么alloca不检查是否可以分配内存?

来自 man 3 alloca:

If the allocation causes stack overflow, program behavior is undefined. … There is no error indication if the stack frame cannot be extended.

为什么alloca不/不能检查它是否可以分配更多内存?

我的理解是 alloca 在堆栈上分配内存,而 (s)brk 在堆上分配内存。来自 https://en.wikipedia.org/wiki/Data_segment#Heap :

The heap area is managed by malloc, calloc, realloc, and free, which may use the brk and sbrk system calls to adjust its size

来自 man 3 alloca:

The alloca() function allocates size bytes of space in the stack frame of the caller.

并且堆栈和堆在会聚方向上增长,如维基百科图表所示:

(上图来自Wikimedia Commons by Dougct released under CC BY-SA 3.0)

现在 alloca(s)brk return 都是指向新分配内存开始的指针,这意味着它们都必须知道当前堆栈/堆在哪里结束片刻。确实,来自 man 2 sbrk:

Calling sbrk() with an increment of 0 can be used to find the current location of the program break.

所以,按照我的理解,检查 alloca 是否可以分配所需的内存本质上归结为检查堆栈的当前末端和当前末端之间是否有足够的 space堆的。如果在栈上分配所需的内存会使栈到达堆,则分配失败;否则成功。

那么,为什么不能使用这样的代码来检查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() sets the end of the data segment to the value specified by addr, when that value is reasonable, the system has enough memory, and the process does not exceed its maximum data size (see setrlimit(2)).

那么,如果 (s)brk 可以进行此类检查,那么为什么 alloca 不能?

图片有点过时:在现代系统上,堆内存区域和包含调用堆栈的内存区域是完全独立的实体,并且在 64 位系统上它们相距甚远。内存中断的概念是为小地址spaces.

的系统设计的

因此,堆栈 space 的限制不是它可能会增长到堆顶,而是内核可能没有任何剩余内存来支持它。或者内核可能会认为您的堆栈增长太多(达到某个限制),从而终止您的进程。

您的进程仅通过递减堆栈指针并在其中存储一些数据来增加堆栈。如果该内存访问当前未映射到您的地址 space,硬件会立即向 OS 内核发出这种情况的信号,内核会检查发生失败的内存访问的位置,以及是否恰好在堆栈内存下方区域,它将立即扩展该内存映射,在那里映射一个新的内存页面,并将控制权交还给您的进程以重试其内存访问。 进程看不到任何这些。它只是看到它访问堆栈内存成功了。

alloca() 不会以任何方式偏离这一点:您要求它在堆栈上分配一些内存,它通过相应地减少堆栈指针来实现。但是,如果您的分配太大以至于 OS 没有将对其的内存访问视为有效的堆栈内存访问,它将(很可能并且希望)使用 SEGFAULT 终止您的进程。但是,由于行为未定义,任何事情都可能发生。

alloca 是一个非标准的编译器内在函数,其卖点是它可以编译为极其轻量级的代码,甚至可能是 single instruction。它基本上执行在每个函数开始时使用局部变量执行的操作 - 将堆栈指针寄存器移动指定的数量和 return 新值。与 sbrk 不同,alloca 完全在用户 space 中,无法知道还有多少堆栈可用。

堆栈向堆增长的图像是学习内存管理基础知识的有用心智模型,但在现代系统上并不十分准确:

  • 正如 cmaster 在他的回答中所解释的那样,堆栈大小将主要受内核强制执行的限制的限制,而不是堆栈实际碰撞到堆中的限制,尤其是在 64 位系统上。
  • 在多线程进程中,不是一个栈,而是一个线程一个栈,显然不可能都向堆增长。
  • 堆本身不是连续的。现代 malloc 实施使用多个领域 improve concurrent performance, and offload large allocations to anonymous mmap, ensuring that free 。后者也在传统描述的单一竞技场之外 "heap"。

可以想象 alloca 的一个版本从 OS 和 return 查询此信息是一个适当的错误条件,但随后它的性能优势将丢失,相当甚至可能与 malloc 相比(它只是偶尔需要去 OS 为进程获取更多内存,并且通常在 user-space 中工作)。