当内存不足时,如何防止变长数组崩溃?

10

在支持变长数组之前,我通常会这样动态分配它们:

int foo(size_t n)
{
    int *arr = malloc(n * sizeof int);
    if (!arr) return ENOMEM; /* not enough memory */
    .
    . else do stuff with arr[]
    .
    free(arr);
    return 0;
}

使用可变长度数组,现在我可以让代码看起来更加简洁:

int bar(size_t n)
{
    int arr[n];
    .
    . do stuff with arr[]
    .
    return 0;
}

但是现在我没有“内存不足”检查。实际上,如果n太大,程序会崩溃。

如果n太大,我如何优雅地从bar(n)中退出?


你的限制可能(当然,这取决于系统)不是“内存”,而是“堆栈大小”。现在我对这个问题更有理解了,因为我在想你在一个相当现代的消费者电脑上的“之前”实现,想知道“他在做什么需要大部分GB?”无论如何,如果堆栈限制了你,那么“旧”的方法可能更好。 - dmckee --- ex-moderator kitten
这正是为什么VLAs没有被广泛使用的原因(另一个原因是编译器支持不佳)。C99 VLAs根本不稳定。 - Vahid Amiri
3个回答

12

这种情况与其他局部变量完全没有变化 - 声明方式如下:

int baz(void)
{
    int arr[100000];
    .
    . do stuff with arr[]
    .
    return 0;
}

有完全相同的问题。 "解决方案" 与以往一样 - 不要递归太深,并且不要使用自动存储期限制非常大的数据结构(在这些情况下继续使用malloc())。 “非常大”的价值强烈取决于您的环境。

换句话说,除非您知道n被限制为合理的值(例如,您将愉快地声明一个最大大小为普通非可变类型数组的数组),否则不要声明int array [n];

(是的,这意味着可变修改类型的数组并不像它们最初看起来那么有用,因为您几乎没有比仅声明具有最大所需大小的数组获得更多的好处)。


这确实让人有了更清晰的认识,谢谢。我可以决定一个最大尺寸,并以 if (n <= MAX) { int arr[n]; ... } 开始函数。 - Henry

6

你可以通过不使用它们来防止崩溃。 :)

说真的,除非你对大小有强烈的边界限制,否则几乎没有安全地使用可变长度数组来使生活更轻松的方法。另一方面,你可以有条件地使用它们,例如:

char vla_buf[n < 1000 ? n : 1];
char *buf = sizeof vla_buf < n ? malloc(n) : vla_buf;
if (!buf) goto error;
/* ... Do stuff with buf ... */
if (buf != vla_buf) free(buf);

虽然看起来毫无用处,但在多线程应用程序中,调用许多mallocfree可能会导致锁争用,这时候使用变长数组可以带来巨大的性能差异。此技巧的一个显著附带好处是,您可以通过将 [n < 1000 ? n : 1] 替换为 1000(例如使用宏)来支持旧编译器而不使用变长数组。

另一个变长数组可能有用的晦涩案例是递归算法,在这种情况下,您知道跨递归所有级别所需的数组条目总数受到 n 的限制,其中 n 足够小,您有信心它不会溢出堆栈,但可能存在多达 n 级递归和单个级别使用高达 n 的元素。在 C99 之前,处理此类情况的唯一方法而不占用 n^2 堆栈空间是使用 malloc。使用变长数组,您可以完全在堆栈上解决问题。

请记住,这些情况下变长数组真正有益的情况非常罕见。通常,变长数组只是一种欺骗自己内存管理很容易的方式,直到您被由此产生的(容易利用)漏洞所咬。

编辑:为了更好地回答 OP 的原始问题:

#define MAX_VLA 10000
int bar(size_t n)
{
    int arr[n <= MAX_VLA ? n : 1];
    if (sizeof arr/sizeof *arr < n) return ENOMEM;
    /* ... */
    return 0;
}

在你的第一个例子中,你可以随处使用[1000],因为你已经确定该值不会(不应该)崩溃。递归算法似乎是真正的用例。 - caf
你给了我一些好的想法,但我认为最简单的方法是在函数开头加上 if (n <= MAX_VLA) { int arr[n]; ... }。谢谢。 - Henry
请注意,您的示例中存在错误。 :-) 应该是 if ((sizeof arr)/(sizeof int) < n) 等等。 - Henry
@caf:我大部分同意。在第一种情况下,您可以节省堆栈空间(并可能提高缓存一致性),但代价是设置一些额外的逻辑。可能不值得,但谁知道呢。 - R.. GitHub STOP HELPING ICE

0

实际上,在每个地方检查内存不足的情况是代价高昂的。处理大量数据的企业方式是通过在单个早期检查点上定义大小硬限制,并在达到限制时快速而优雅地失败来限制数据大小。

我刚才提出的建议很简单,很愚蠢。但这是每个普通(非科学或特殊)产品都做的事情。这也是客户通常期望的。


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