alloca()函数分配的内存能否重新分配?

9

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

重要的是所有这些操作都在堆栈上完成。 问题:有没有一种方法可以重新分配动态堆栈内存?


1
realloc的manpage提到,如果指针不是之前由malloccallocrealloc返回的,则行为未定义。 - Gerhardh
2
通常不建议使用本地堆栈内存。alloca()本身被认为是不安全的。请注意,可用空间取决于嵌套例程的数量,并且内存违规始终是致命的。此外,当您离开本地函数时,该内存将不可用。如果您只想在本地使用内存(每个DLL都将有一个本地堆),则堆分配策略不是问题,如果您想全局使用它,则该方法出错的原因如前所述。 - Frankie_C
2个回答

11

不行:这在堆栈通常实现的情况下是行不通的。堆栈上的变量占用一定范围的地址。下一个变量紧随其后,因此没有扩展的空间。考虑像这样的函数:

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

你的图表不太准确。a 本身就是一个指针,在栈上可能占用32或64位。由 alloca 分配的对象是匿名的,并且大小为160字节(假设 sizeof(float) == 4)。 - Keith Thompson
-1 那个堆栈布局不正确。 - Cheers and hth. - Alf
@Cheersandhth.-Alf,您介意详细说明一下它的问题吗?它并没有阐述具体的架构,只是一个概念模型,所以我不知道您会根据什么来评估其正确性。 - Gilles 'SO- stop being evil'
@Gilles'SO-stopbeingevil':主要问题在于您已经将分配的数组放置在其他内容之间。实际上并不是这样:指针a在那里,但它指向的分配数组位于堆栈的开放端,堆栈中的“顶部”即为其术语。编译器知道的所有名称都位于堆栈帧中的固定偏移量处,没有一个名称被alloca移动。这使答案中的逻辑无效,这就是Just Wrong™(完全错误)。 - Cheers and hth. - Alf
你的回答是“不行:这在栈的常见实现中行不通”。这不是“小问题”,而是完全错误的。我已经标记它以引起管理员的注意。 - Cheers and hth. - Alf
显示剩余3条评论

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接