(Background: I have some experience implementing C and C++ compilers.)
Variable-length arrays in C99 were basically a misstep. In order to support VLAs, C99 had to make the following concessions to common sense:
sizeof x
is no longer always a compile-time constant; the compiler must sometimes generate code to evaluate a sizeof
-expression at runtime.
Allowing two-dimensional VLAs (int A[x][y]
) required a new syntax for declaring functions that take 2D VLAs as parameters: void foo(int n, int A[][*])
.
Less importantly in the C++ world, but extremely important for C's target audience of embedded-systems programmers, declaring a VLA means chomping an arbitrarily large chunk of your stack. This is a guaranteed stack-overflow and crash. (Anytime you declare int A[n]
, you're implicitly asserting that you have 2GB of stack to spare. After all, if you know "n
is definitely less than 1000 here", then you would just declare int A[1000]
. Substituting the 32-bit integer n
for 1000
is an admission that you have no idea what the behavior of your program ought to be.)
Okay, so let's move to talking about C++ now. In C++, we have the same strong distinction between "type system" and "value system" that C89 does… but we've really started to rely on it in ways that C has not. For example:
template<typename T> struct S { ... };
int A[n];
S<decltype(A)> s; // equivalently, S<int[n]> s;
If n
weren't a compile-time constant (i.e., if A
were of variably modified type), then what on earth would be the type of S
? Would S
's type also be determined only at runtime?
What about this:
template<typename T> bool myfunc(T& t1, T& t2) { ... };
int A1[n1], A2[n2];
myfunc(A1, A2);
The compiler must generate code for some instantiation of myfunc
. What should that code look like? How can we statically generate that code, if we don't know the type of A1
at compile time?
Worse, what if it turns out at runtime that n1 != n2
, so that !std::is_same<decltype(A1), decltype(A2)>()
? In that case, the call to myfunc
shouldn't even compile, because template type deduction should fail! How could we possibly emulate that behavior at runtime?
Basically, C++ is moving in the direction of pushing more and more decisions into compile-time: template code generation, constexpr
function evaluation, and so on. Meanwhile, C99 was busy pushing traditionally compile-time decisions (e.g. sizeof
) into the runtime. With this in mind, does it really even make sense to expend any effort trying to integrate C99-style VLAs into C++?
As every other answerer has already pointed out, C++ provides lots of heap-allocation mechanisms (std::unique_ptr<int[]> A = new int[n];
or std::vector<int> A(n);
being the obvious ones) when you really want to convey the idea "I have no idea how much RAM I might need." And C++ provides a nifty exception-handling model for dealing with the inevitable situation that the amount of RAM you need is greater than the amount of RAM you have. But hopefully this answer gives you a good idea of why C99-style VLAs were not a good fit for C++ — and not really even a good fit for C99. ;)
For more on the topic, see N3810 "Alternatives for Array Extensions", Bjarne Stroustrup's October 2013 paper on VLAs. Bjarne's POV is very different from mine; N3810 focuses more on finding a good C++ish syntax for the things, and on discouraging the use of raw arrays in C++, whereas I focused more on the implications for metaprogramming and the typesystem. I don't know if he considers the metaprogramming/typesystem implications solved, solvable, or merely uninteresting.
A good blog post that hits many of these same points is "Legitimate Use of Variable Length Arrays" (Chris Wellons, 2019-10-27).
In C const
doesn't mean "constant" (i.e., evaluable at compile time). It merely means read-only.
For example, within a function, this:
const int r = rand();
const time_t now = time(NULL);
is perfectly valid.
The name of an object defined as const int
is not a constant expression. That means that (in C prior to C99, and in all versions of C++) it can't be used to define the length of an array.
Although C99 (and, optionally, C11) support variable-length arrays (VLAs), they can't be initialized. In principle, the compiler doesn't know the size of a VLA when it's defined, so it can't check whether an initializer is valid. In your particular case, the compiler quite probably is able to figure it out, but the language rules are designed to cover the more general case.
C++ is nearly the same, but C++ has a special rule that C lacks: if an object is defined as const
, and its initialization is a constant expression, then the name of the object it itself a constant expression (at least for integral types).
There's no really good reason that C hasn't adopted this feature. In C, if you want a name constant of an integer type, the usual approach is to use a macro:
#define LEN 5
...
int arr[LEN] = {1, 2, 3, 4, 5};
Note that if you change the value of LEN
, you'll have to re-write the initializer.
Another approach is to use an anonymous enum
:
enum { LEN = 5 };
...
int arr[LEN] = {1, 2, 3, 4, 5};
The name of an enumeration constant is actually a constant expression. In C, for historical reasons, it's always of type int
; in C++ it's of the enumeration type. Unfortunately, this trick only works for constants of type int
, so it's restricted to values in the range from INT_MIN
to INT_MAX
.
Best Answer
It is a quirk of the syntax for passing arrays to functions.
Actually it is not possible to pass an array in C. If you write syntax that looks like it should pass the array, what actually happens is that a pointer to the first element of the array is passed instead.
Since the pointer does not include any length information, the contents of your
[]
in the function formal parameter list are actually ignored.The decision to allow this syntax was made in the 1970s and has caused much confusion ever since...