简体   繁体   English

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

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

Why does alloca not check if it can allocate memory? 为什么alloca不检查它是否可以分配内存?

From man 3 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. ...如果无法扩展堆栈帧,则没有错误指示。

Why alloca does not / can not check if it can allocate more memory? 为什么alloca不能/不能检查它是否可以分配更多内存?

The way I understand it alloca allocates memory on stack while (s)brk allocates memory on the heap. 我理解它的方式alloca在堆栈上分配内存,而(s)brk在堆上分配内存。 From https://en.wikipedia.org/wiki/Data_segment#Heap : 来自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 堆区域由malloc,calloc,realloc和free管理,可以使用brk和sbrk系统调用来调整其大小

From man 3 alloca : man 3 alloca

The alloca() function allocates size bytes of space in the stack frame of the caller. alloca()函数在调用者的堆栈帧中分配空间的大小字节。

And the stack and heap are growing in the converging directions, as shown in this Wikipedia graph: 并且堆栈和堆在收敛方向上增长,如此Wikipedia图中所示:

在此输入图像描述

(The above image is from Wikimedia Commons by Dougct released under CC BY-SA 3.0 ) (上图来自Dougimed的Wikimedia Commons ,在CC BY-SA 3.0下发布)

Now both alloca and (s)brk return a pointer to the beginning of the newly allocated memory, which implies they must both know where does the stack / heap end at the current moment. 现在, alloca(s)brk返回一个指向新分配内存开头的指针,这意味着它们必须都知道堆栈/堆在当前时刻的结束位置。 Indeed, from man 2 sbrk : 确实,从man 2 sbrk

Calling sbrk() with an increment of 0 can be used to find the current location of the program break. 以增量0调用sbrk()可用于查找程序中断的当前位置。

So, they way I understand it, checking if alloca can allocate the required memory essentially boils down to checking if there is enough space between the current end of the stack and the current end of the heap. 因此,他们理解它,检查alloca可以分配所需的内存,实质上归结为检查堆栈的当前末尾和堆的当前末尾之间是否有足够的空间。 If allocating the required memory on the stack would make the stack reach the heap, then allocation fails; 如果在堆栈上分配所需的内存会使堆栈到达堆,则分配失败; otherwise, it succeeds. 否则,它会成功。

So, why can't such a code be used to check if alloca can allocate memory? 那么,为什么不能使用这样的代码来检查alloca可以分配内存?

void *safe_alloca(size_t size)
{
    if(alloca(0) - sbrk(0) < size) {
        errno = ENOMEM;
        return (void *)-1;
    } else {
        return alloca(size);
    }
}

This is even more confusing for me since apparently (s)brk can do such checks. 这对我来说更加困惑,因为显然(s)brk可以做这样的检查。 From man 2 sbrk : 来自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)). brk()将数据段的结尾设置为addr指定的值,当该值合理时,系统有足够的内存,并且进程不超过其最大数据大小(请参阅setrlimit(2))。

So if (s)brk can do such checks, then why can't alloca ? 那么,如果(s)brk可以做这样的检查,那么为什么不能alloca

alloca is a non-standard compiler intrinsic whose selling point is that it compiles to extremely lightweight code, possibly even a single instruction . alloca是一个非标准的编译器内在,其卖点是它编译成非常轻量级的代码,甚至可能是单个指令 It basically does the operation performed at the beginning of every function with local variables - move the stack pointer register by the specified amount and return the new value. 它基本上使用局部变量在每个函数的开头执行操作 - 将堆栈指针寄存器移动指定的量并返回新值。 Unlike sbrk , alloca is entirely in userspace and has no way of knowing how much stack is left available. sbrk不同, alloca完全在用户空间中,无法知道剩余多少堆栈。

The image of stack growing towards the heap is a useful mental model for learning the basics of memory management, but it is not really accurate on modern systems: 堆栈向堆堆积的图像是学习内存管理基础知识的有用心智模型,但它在现代系统上并不真实:

  • As cmaster explained in his answer, the stack size will be primarily limited by the limit enforced by the kernel, not by the stack literally colliding into the heap, especially on a 64-bit system. 正如cmaster在他的回答中所解释的那样,堆栈大小将主要受内核强制执行的限制,而不是堆栈直接冲入堆中,特别是在64位系统上。
  • In a multi-threaded processes, there is not one stack, but one for each thread, and they clearly cannot all grow towards the heap. 在多线程进程中,没有一个堆栈,但是每个线程都有一个堆栈,并且它们显然不能全部向堆增长。
  • The heap itself is not contiguous. 堆本身不是连续的。 Modern malloc implementations use multiple arenas to improve concurrent performance , and offload large allocations to anonymous mmap , ensuring that free returns them to the OS . 现代malloc实现使用多个竞技场来提高并发性能 ,并将大量分配卸载到匿名mmap ,确保free 将它们返回给操作系统 The latter are also outside the single-arena "heap" as traditionally depicted. 后者也在传统描述的单竞技场“堆”之外。

It is possible to imagine a version of alloca that queries this information from the OS and returns a proper error condition, but then its performance edge would be lost, quite possibly even compared to malloc (which only occasionally needs to go to the OS to grab more memory for the process, and usually works in user-space). 有可能想象一个版本的alloca从操作系统查询这些信息并返回一个正确的错误条件,但是它的性能优势会丢失,甚至可能与malloc相比(它偶尔需要去操作系统才能获取)为进程提供更多内存,通常在用户空间中工作)。

The picture is a bit outdated: On a modern system, the heap memory regions and the memory region that contains the call stack are entirely separate entities, and they are very far apart on a 64 bit system. 图片有点过时:在现代系统中,堆内存区域和包含调用堆栈的内存区域是完全独立的实体,它们在64位系统上相距很远。 The concept of the memory break was designed for systems with small address spaces. 内存中断的概念是为具有小地址空间的系统设计的。

As such, the limitation for the stack space is not that it may grow into the top of the heap, the limitation is that the kernel may not have any memory left to back it. 因此,堆栈空间的限制并不是它可能会增长到堆的顶部,限制是内核可能没有任何内存可以支持它。 Or the kernel may decide that your stack has grown too much (some limit is reached), and thus shoots down your process. 或者内核可能会判断您的堆栈已经增长太多(达到某个限制),从而导致您的进程崩溃。

Your process grows the stack simply by decrementing the stack pointer, and storing some data there. 您的进程只需通过递减堆栈指针并在那里存储一些数据来增加堆栈。 If that memory access is currently not mapped into your address space, the hardware immediately signals this condition to the OS kernel, which checks where the failed memory access occurred, and if that is just below the stack memory region, it will immediately expand that memory mapping, map a new page of memory there, and give control back to your process to retry its memory access. 如果该内存访问当前未映射到您的地址空间,则硬件会立即向OS内核发出此状态信号,该内核会检查发生内存访问失败的位置,如果它位于堆栈内存区域之下,则会立即扩展该内存映射,在那里映射新的内存页面,并将控制权交还给您的进程以重试其内存访问。 The process does not see any of this . 该过程没有看到任何此类 It just sees that its access to stack memory succeeded. 它只是看到它对堆栈内存的访问成功了。

alloca() does not deviate from this in any way: You ask it to allocate some memory on the stack, and it does so by decrementing the stack pointer accordingly. alloca()不会以任何方式偏离此:您要求它在堆栈上分配一些内存,并通过相应地递减堆栈指针来实现。 However, if your allocation is so large that the OS does not see memory accesses to it as valid stack memory accesses, it will (likely and hopefully) kill your process with a SEGFAULT. 但是,如果您的分配太大以至于操作系统没有看到对它的内存访问作为有效的堆栈内存访问,它将(可能并且希望)使用SEGFAULT来终止您的进程。 However, since the behavior is undefined, anything may happen. 但是,由于行为未定义,任何事情都可能发生。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM