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

时间:2019-05-31 10:05:38

标签: c memory-management realloc alloca

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

重要的是,这一切都发生在堆栈上。 问:有没有办法重新分配动态堆栈内存?

2 个答案:

答案 0 :(得分:9)

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

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。那就是他们的目的。

在某些环境中,支持自定义分配器很有用。您可以为库的用户提供选项,以将指针传递到mallocreallocfree的替代实现。当您要编写一个可移植的库,而该库需要由完全可移植的代码使用时,此功能很有用。但是,在大多数情况下,想要使用自定义分配器的用户可以通过链接自己的malloc和朋友来做到这一点。甚至也很少有用。

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

/** [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;
    …
}

答案 1 :(得分:3)

已被接受的答案正确地指出,realloca通常没有足够的好处,因为分配很难“增长”。

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

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

有人认为可以以这样一种方式定义语义,即只能在alloc所在的函数中重新分配该函数。这大大减少了该函数的使用范围。