17
$\begingroup$

At first C did not have variable-length arrays; all array sizes must be compile times constants. Then, in C99, variable-length arrays, a controversial addition, were added, permitting runtime-sized stack arrays.

int x;
scanf("%d", x);
int vla[x];

The addition had implications such as sizeof() being able to evaluate arguments at runtime. But is this implication avoidable? Why are variable-length arrays such a controversial feature? What are the implications and catches of implementing variable-length arrays, and how do compilers usually get around them or avoid them?

$\endgroup$
1
  • 3
    $\begingroup$ Ooh, our first bounty is on this question $\endgroup$ Jun 12, 2023 at 10:54

3 Answers 3

7
+50
$\begingroup$

VLAs were added to C99, but made optional in C11. The reason they were made optional doesn't appear to be in public documents.

The initial proposal to add VLA to C (N637) cited numerical computing as the motivation, and the proposal was based on an existing implementation on Cray platforms. In terms of burden on implementers, a similar feature existed on many platforms via the function alloca, which is easy to implement on most platforms where the local variables of a function are on a stack that's a contiguous block of memory (which is the case on almost all platforms). Nonetheless, at least one major C implementation cited VLA as a low-priority feature: in Visual C++, they were one of the last missing C99 features and they will not be added.

I think (but I don't have insider information) that the difficulties with VLA aren't in implementations, but for users. VLAs are bug-prone for many reasons. An obvious one is that they can very easily lead to a stack overflow, which often has disastrous consequences (at best a crash, often memory corruption). Another consequence is that innocuous-looking constructs such as sizeof(...) may now have side effects, and even a simple sizeof(a) doesn't actually return the size of the array a if it's a VLA function parameter (that's true if it's a function parameter as well with a static size, but VLAs make it look more like it should work).

Another reason against having VLA in the C standard is that according to John Nagle on comp.std.c,

Prototypes of functions with VLA parameters do not have to exactly match the function definition. This is incompatible with C++ style linkage and C++ function overloading, preventing the extension of this feature into C++.

C++ is mostly an extension of C; it tweaks some behavior (enough that most idiomatic C programs aren't valid C++) but having a major feature of C that's not in the next edition of C++ is unusual, and is problematic for compilers that handle both languages (which is most C++ compilers).

From personal experience in as a C programmer on embedded platforms, we never use VLAs, even when we're sure that all compilers we care about would support them. They're too bug-prone and they make it impossible to predict your program's stack size (we like to make sure that the deepest nesting of function calls will not overflow the stack — we don't use recursion either). Jens Gustedt on comp.std.c also cites GPUs as a type of platforms where VLAs aren't welcome (I don't know exactly why, but I guess because the memory assigned to each thread's stack tends to be limited).

$\endgroup$
3
  • $\begingroup$ "in Visual C++, they were one of the last C99 features to be added." -- It's my understanding that it still doesn't fully implement C99, if nothing else because it doesn't understand the restrict keyword (you have to write __restrict instead). $\endgroup$
    – Bbrk24
    Jun 16, 2023 at 19:22
  • $\begingroup$ @Bbrk24 In fact VS doesn't implement VLAs at all (I realize what I wrote is a bit misleading). They never really tried to implement C99 and went directly to C11. $\endgroup$ Jun 16, 2023 at 20:21
  • $\begingroup$ No version of the C Standard has ever specified as a condition of conformance that an automatic-duration object definition of the form int arr[n] behave meaningfully for any particular value of n, nor specified what should happen if n exceeds the maximum value an implementation can handle. The only real effect of making the feature optional is that implementations that wouldn't otherwise support VLAs of non-trivial size needn't waste effort pretending to support them, and can instead reject at compile time programs that would be unlikely to behave usefully at run time. $\endgroup$
    – supercat
    Dec 13, 2023 at 23:15
11
$\begingroup$

I'd say one of the reasons is that it was a rather niche feature with a very natural syntax (which you usually try to avoid). Most times I've seen VLAs used in code, it was by novice programmers who didn't care much about how it worked and just preferred it to the more verbose heap allocations. Another common case is people intending to encapsulate the array size in a compile-time variable, but forgetting(/not realizing the need) to mark it as constexpr or at least const. There's little benefit to a feature like this, and it introduces confusion to users and complexity to the language (like, as you've mentioned, having sizeof operate at runtime in some cases). It's a much more fitting solution to provide the alloca function as the API for stack allocations for cases when it is actually needed, and have the programmers handle the resulting variable-length array by pointer, same as in other cases.

As for whether the implications can be avoided, I'd say, possibly, but not in the general case. If, for instance, the base-pointer optimization is enabled (making the local variables be resolved through the stack pointer instead of the base pointer), any VLA significantly slows down the local variable access (as it needs to perform at least one more operation every time). Overall, perhaps some clever tricks could be introduced, but VLAs seem to be of too little use to overcome such difficulties for.

Also worth noting is that a similar concept of variable-sized structs (i.e. having an array extend past the end of a struct) is a widely used idiom in C/C++, despite having almost no specific language support (like no special cases for sizeof nor automatic copying).

TL;DR: alloca does the trick just as well, but is less confusing both for users and language designers

$\endgroup$
3
$\begingroup$

I recall this feature being a GNU extension for a long time before becoming standardized. I suspect it was implemented using alloca() or similar to allocate space on the stack.

This is certainly born out by the Wikipedia page on the subject.

There is a very good quality answer to this question here.

It's easily missed as its asked in the context of C++.

The big danger is the simple and obvious implementation is dangerous. For example:

foo(int n)
{
   int someArrayOnTheStack[n];
   // do stuff
}

You can easily overflow the stack if n is big. In our security conscious world that's a buffer overflow exploit waiting to happen.

You can get around that by allocating on the heap instead but that defies users expectations as it looks like a stack declaration but magically becomes a heap one with no visible call to malloc().

Of course in C++ you'd just write:

foo(int n)
{
   std::vector<int> someArray(n);
   // do stuff
}

Which matches expectations and so the feature is unnecessary there.

The linked Q&A points out some other issues.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .