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

f87krz0w  于 2023-05-28  发布在  其他
关注(0)|答案(2)|浏览(199)

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

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

js81xvg6

js81xvg61#

否:这不适用于通常实现的堆栈。堆栈上的变量占用固定的地址范围。下一个变量紧跟着它,所以没有增长的空间。考虑这样一个函数:

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的用途。
作为一个实际问题,有几种可能的方法来动态管理库中的内存。
最常见的方法是在需要时调用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;
    …
}
ac1kyiln

ac1kyiln2#

公认的答案已经正确地指出,通常从realloca中没有足够的好处,因为分配很难“增长”。
我看到的另一个问题是,这些分配有一个生命周期,直到函数结束。当你把这个指针传递给另一个函数并在那里调用realloca时会发生什么?此函数将无法更改堆栈上更深层的函数的堆栈框架。它也不能在自己的帧中重新分配它,因为当它返回时,对象将被销毁,而原始对象仍然必须是活动的。
malloc/realloc不存在这个问题,因为堆具有全局生存期。
有人可能会说,语义可以这样定义:一个函数只能在它所在的函数中被重新分配。这大大减少了这种功能的使用。

相关问题