alloca() 内存可以重新分配吗?

Can alloca() memory be reallocated?

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                 | k   | ...                 
----------------+-----+-----+-----+-------------------+-----+---------------------
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
previous frames                    f's frame                 free space at the top

没有增长空间a

我展示了一个高度简化的例子:在现实世界中,变量最终进入寄存器,变量可以重新排序,即使它们最终进入堆栈,等等。但只有一个变量可以是最后一个在有增长空间的堆栈上。

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

某处的某些 C 实现可能有 realloca,但考虑到 cost/benefit 比率,这不太可能。

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


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

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

在某些环境中,支持自定义分配器很有用。您可以为库的用户提供将指针传递给 mallocreallocfree 的替代实现的选项。当您想要编写一个需要由本身完全可移植的代码使用的可移植库时,它很有用。不过,大多数时候,想要使用自定义分配器的用户可以通过链接他们自己的 malloc 和朋友来实现。甚至 that 也很少有用。

如果您需要可以在没有动态分配的环境(例如 safety-critical 环境)中工作的代码,那么您也不应该使用 allocaallocamalloc 更糟糕,因为它会导致不可预测的堆栈使用,并可能导致根本无法检测到的堆栈溢出,或者只能通过程序崩溃检测到。如果您在函数中需要可变(或大量)临时内存,请让用户将 suitably-sized 缓冲区传递给您。

/** [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 通常没有足够的好处,因为分配很难 "grow"。

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

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

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