How do you prevent variable-length arrays from cra

2019-02-16 15:42发布

问题:

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?

回答1:

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



回答2:

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


回答3:

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.