8

Memory allocated by malloc can be reallocated with realloc. Is there a similar function for alloca? Reallocating stack memory could be useful when you don't want memory to be allocated on the heap, and you need to allocate variable stack memory multiple times, for example in a library function, where you need dynamic memory, but don't want to allocate on the heap, because the user of the library might use a custom heap allocation strategy. It would look like this:

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));
}

The important thing is that this all happens on the stack. Q: is there a way to reallocate dynamic stack memory?

4

2 回答 2

11

不:这不适用于通常实现的堆栈。堆栈上的变量占用固定范围的地址。下一个变量紧随其后,因此没有增长空间。考虑这样的函数:

void f(int x) {
    int i;
    float *a = alloca(40 * sizeof(float));
    int k;
    …
}

函数序言之后的堆栈如下所示:

----------------+-----+-----+-----+-------------------+-----+---------------------
...             | ret | x   | i   | a                 | k   | ...                 
----------------+-----+-----+-----+-------------------+-----+---------------------
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
previous frames                    f's frame                 free space at the top

没有成长的空间a

我正在展示一个高度简化的示例:在现实世界中,变量最终位于寄存器中,即使变量最终位于堆栈中,也可以重新排序,等等。但只有一个变量可以是堆栈中最后一个有空间的变量成长。

因此,如果realloca存在,它只能应用于堆栈顶部的变量。(否则它必须移动它上面的所有其他东西,但这需要更新所有现有指针,这通常是不可能的。)这将是一个非常有限的机制,因此对这个特性的支持将一个很小的好处。支持它会产生很大的成本,因为编译器通常可以按照他们想要的顺序自由地把东西放在堆栈上:这个特性需要一种新的机制来让编译器知道一个特定的变量必须放在顶部。

某处的某些 C 实现可能具有realloca,但考虑到成本/收益比,这不太可能。

如果不使用堆栈分配策略当然realloca可以轻松实现。alloca但是在堆栈上分配是alloca. 如果你想要可调整大小的对象,你需要一个带有堆接口的内存管理结构,这就是malloc目的。


实际上,库中的动态内存管理有几种可能的方法。

最常见的方法是调用mallocreallocfree在需要时调用。这就是他们的目的。

在某些环境中,支持自定义分配器很有用。您可以让库的用户选择将指针传递给和malloc的替代实现。当您想要编写一个需要由本身完全可移植的代码使用的可移植库时,它很有用。但是,大多数时候,想要使用自定义分配器的用户可以通过链接自己的和朋友来实现。即使这样也很少有用。reallocfreemalloc

如果您需要可以在没有动态分配的环境(例如安全关键环境)中工作的代码,那么您不应该使用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;
    …
}
于 2019-05-31T10:37:56.173 回答
3

公认的答案正确地指出,realloca由于分配难以“增长”,因此通常没有足够的好处。

我看到的另一个问题是,这些分配在函数结束之前都有生命周期。当您将此指针传递给另一个函数并realloca在那里调用它时会发生什么?此函数将无法更改堆栈中更深的函数的堆栈帧。它也不能在自己的框架中重新分​​配它,因为当它返回时对象将被销毁,而原始对象仍然必须是活着的。

这个问题不存在,malloc/realloc因为堆具有全局生命周期。

有人可能会争辩说,可以以这样一种方式定义语义,即一个函数只能在它所在的函数中重新分配alloc。这大大减少了这样一个函数的使用。

于 2019-05-31T12:56:03.183 回答