malloc
分配的内存可以使用realloc
重新分配。alloca
有类似的功能吗?当您不希望在堆上分配内存,并且需要多次分配可变堆栈内存时,重新分配堆栈内存可能很有用,例如在库函数中,您需要动态内存,但又不想在堆上分配,因为库的用户可能使用自定义堆分配策略。它看起来像这样:
int main(void) {
float * some_mem = alloca(40 * sizeof(float));
// do something with this memory...
// now we need a different amount of memory, but some_mem still occupies a lot of the stack, so just reallocate it.
// is something like this possible?
some_mem = realloca(some_mem, 50 * sizeof(float));
}
重要的是,这一切都发生在堆栈上。问:有没有办法重新分配动态堆栈内存?
不:这不适用于通常实现的堆栈。堆栈上的变量占用固定的地址范围。下一个变量紧随其后,因此没有增长空间。考虑这样的函数:
void f(int x) {
int i;
float *a = alloca(40 * sizeof(float));
int k;
…
}
函数序幕后面的堆栈如下所示:
----------------+-----+-----+-----+-----+-------------------+-----+---------------------
... | ret | x | i | a | a[] | k | ...
----------------+-----+-----+-----+-----+-------------------+-----+---------------------
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
previous frames f's frame free space at the top
没有成长的空间a
.
我展示了一个高度简化的例子:在现实世界中,变量最终出现在寄存器中,变量可以重新排序,即使它们最终出现在堆栈上,等等。但只有一个变量可以是堆栈上最后一个有增长空间的变量。
因此,如果realloca
存在,它只能应用于堆栈顶部的变量。(否则,它必须移动其顶部的其他所有内容,但这需要将所有现有指针更新到这些指针,这通常是不可能的。这将是一个非常有限的机制,因此支持此功能的好处很小。支持它将产生巨大的成本,因为编译器通常可以按照他们想要的顺序自由地将内容放在堆栈上:此功能需要一种新的机制来让编译器知道一个特定的变量必须转到顶部。
某些 C 实现可能在某处有realloca
,但考虑到成本/收益比,这不太可能。
当然,如果alloca
不使用堆栈分配策略,realloca
可以轻松实现。但是在堆栈上分配是alloca
的重点.如果你想要可调整大小的对象,你需要一个带有堆接口的内存管理结构,这就是malloc
的用途。
实际上,库中有几种可能的动态内存管理方法。
最常见的方法是在需要时调用malloc
、realloc
和free
。这就是他们的目的。
在某些环境中,支持自定义分配器非常有用。您可以为库的用户提供将指针传递给malloc
、realloc
和free
的替代实现的选项。当您想要编写需要由本身完全可移植的代码使用的可移植库时,它很有用。但是,大多数时候,想要使用自定义分配器的用户可以通过链接自己的malloc
和朋友来实现。即使这样也很少有用。
如果您需要可以在没有动态分配的环境(例如安全关键环境)中工作的代码,则也不应使用alloca
。alloca
比malloc
更糟糕,因为它会导致不可预测的堆栈使用,并可能导致堆栈溢出,根本不会被检测到,或者只能由程序崩溃检测到。如果函数中需要可变(或大量)临时内存,请让用户将适当大小的缓冲区传递给您。
/** [documentation of the function] …
* working_buffer must point to an array of floats of 3*n elements.
*/
void f(size_t n, float *working_buffer);
更好的是,如果您有代码大小预算,请传递数组大小并进行验证。
/** [documentation of the function] …
* working_buffer must point to an array of floats of 3*n elements.
*/
int f(size_t n, float *working_buffer, size_t working_buffer_length)
{
if (working_buffer_length < 3 * n) return -EINVAL;
…
}
公认的答案正确地指出,通常realloca
没有足够的好处,因为分配很难"增长"。
我看到的另一个问题是,这些分配在函数结束之前有一个生命周期。当您将此指针传递给另一个函数并在那里调用realloca
时会发生什么?此函数将无法在堆栈更深的位置更改函数的堆栈帧。它也不能在自己的帧中重新分配它,因为对象在返回时会被销毁,而原始对象仍然必须处于活动状态。
此问题不存在malloc/realloc
因为堆具有全局生存期。
有人可能会争辩说,语义可以这样定义,即函数只能在它alloc
的函数中重新分配。这大大减少了此类函数的使用。