How do you prevent variable-length arrays from crashing when there is not enough memory?
Asked Answered
S

3

10

Before variable-length arrays were supported, I would dynamically allocate them like this:

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

With variable-length arrays I can now make it look cleaner:

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

But now I have no "out of memory" checking. In fact, the program crashes if n is too big.

How can I gracefully bail from bar(n) if n is too big?

Spew answered 11/11, 2010 at 3:37 Comment(2)
Your bound may (system dependent, of course) not be "memory" but "size of the stack". And now the question makes more sense to me because I was thinking of your "before" implementation on a reasonably modern consumer box and wondering "What's he doing that he need a big part of a GB?". Anyway. If the stack limits you, the "old" way may be better.Wildwood
This is exactly the reason why VLAs are not used that much (another being poor support in compilers). C99 VLAs are simply not stable.Virga
G
12

The situation is exactly unchanged from any other local variables - a declaration like this:

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

has exactly the same problem. The "solution" is the same as it always has been - don't recurse too deeply, and don't allocate very large data structures with automatic storage duration (continue to use malloc() for these cases). The value of "very large" depends strongly upon your environment.

In other words, don't declare int array[n]; unless you know that n is bounded to a reasonable value, such that you would have been happy to declare an array of that maximum size as an ordinary, non-variably-modified type array.

(Yes, this means that variably-modified type arrays are not as useful as they first appear, since you gain very little over just declaring the array at the maximum needed size).

Golden answered 11/11, 2010 at 3:41 Comment(1)
This does put it in perspective, thanks. I could decide on a maximum size and begin the function with if (n <= MAX) { int arr[n]; ... }Spew
B
6

You can prevent them from crashing by not using them. :)

In all seriousness, there is almost no safe way to use variable-length arrays to make your life easier unless you have strong bounds on the size. On the other hand, you can use them conditionally, in ways like this:

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

While this looks like useless pain, it can make a huge performance difference, especially in threaded applications where many calls to malloc and free could result in lock contention. (A notable side benefit of this trick is that you can support old compilers without VLAs by simply replacing [n < 1000 ? n : 1] with 1000, e.g. with a macro.)

Another obscure case where VLAs may be useful is in recursive algorithms where you know the total number of array entries required across all levels of recursion is bounded by n, where n is small enough you're confident it won't overflow the stack, but where there could be up to n levels of recursion and individual levels that use up to n elements. Prior to C99, the only way to handle this case without taking n^2 stack space was to use malloc. With VLAs, you can solve the problem entirely on the stack.

Keep in mind, these cases where VLAs are really beneficial are pretty damn rare. Normally VLA is just a way to deceive yourself that memory management is easy, until you get bit by the resulting (trivial-to-exploit) vulnerabilities you've created. :-)

Edit: To better address OP's original question:

#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;
}
Boleslaw answered 11/11, 2010 at 3:53 Comment(4)
In your first example, you might as well just use [1000] everywhere, since you've already determined that that value won't (shouldn't) crash. The recursive algorithm appears to be the real use case.Golden
You gave me some good ideas, but I think the easiest is to simply start the function with if (n <= MAX_VLA) { int arr[n]; ... }. Thanks.Spew
Note that there are bugs in your examples. :-) It should be if ((sizeof arr)/(sizeof int) < n), etc.Spew
@caf: I mostly agree. In the first case, you save stack space (and possibly improve cache coherency) at the expense of some extra logic for setting up. Probably not worth it, but who knows.Boleslaw
D
0

In reality it is prohibitively expensive to check for out of memory conditions everywhere. The enterprisy way to deal with massive data is to limit data sizes by defining hard cap on size at single early checkpoint and fail fast and gracefully when the cap is hit.

What I just suggested is simple and stupid. But its what every ordinary (non-scientific or special) product always does. And its what normally is expected by customer.

Dayak answered 11/11, 2010 at 5:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.