I don't mind. In practice, VLAs tend to have OS-dependent practical limits (specifically, stack size or stack size growth mechanism) that make them not very useful anyway.
^^^ The same strange fallacy based on complete lack of understanding what VLA is and what its purpose is. VLA have absolutely no issues with "practical limits" and no connection to "stack size or stack size growth mechanism" whatsoever.
Fallacy my ass. Even the C standard itself makes a distinction between "variable length array" and "variably modified types", even though they are controlled by the exact same mechanism, and closely related. You took a shortcut, and claim the two are the exact same thing.
When you declare an array size in a function prototype, for example
double determinant(size_t rows, size_t cols, double matrix[rows][cols]);the third parameter has a variably modified type. While you claim this is
essential, it is extremely rarely used in existing C or POSIX C code, because explicitly specifying the data stride is just much more versatile:
#define DET(m) (determinant(sizeof (m) / sizeof *(m), sizeof *(m) / sizeof **(m), sizeof *(m) / sizeof **(m), 1, (const double *)(m))) double determinant(size_t rows, size_t cols, ssize_t rowstride, ssize_t colstride, double *origin);since the latter supports both row-major and column-major data orders (and trivial transpose), submatrices, and so on. The
DET() preprocessor macro calls the function with the correct parameters, when a two-dimensional double array variable is specified; this avoids problems with the array dimensions (a typical problem, when the programmer changes the array dimensions in one place, but forgets to update them everywhere).
For what it is worth, I have no problems with either of the above, but the former does fall under the same optional support depending on
__STDC_NO_VLA__. For linear algebra, I have a very nice library approach that hides the complexity in two structures – one describing the matrices, and the other containing the matrix data in refcounted form – with views and submatrices indistinguishable from primary matrices; the outline of which I've described in e.g.
here. I am also very familiar with BLAS and LAPACK (and the Intel and AMD math libraries that implement them) and GSL, and have contributed to dozens of projects from the GNU C library to the Linux kernel, and have examined the sources of hundreds if not thousands of open source C projects, so I do claim I know pretty well what kind of code is actually used in practice,
and why. Theory is one thing; practical matters are much more important in real life.
What has become much more common in existing C or POSIX C code, is declaring variables of variably modified types, "local variable length arrays":
void foo(size_t rows, size_t cols) { double cache[rows][cols];exactly because people think "malloc() is expensive and prone to bugs". This is the problematic case, and is not supported by the C compiler if it defines
__STDC_NO_VLA__.
For multi-threaded POSIX C programs, the stack often has a relatively small fixed size limit; which means that single-threaded code (due to the automatically growing stack) has completely different stack size limit than multithreaded code, making silly developers think "oh, multithreaded code is just too hard and unpredictable".
The stack access scheme for these has the same issue as large local variable arrays on stack on many OSes, including Linux. When the stack has no defined upper size limit, in virtual memory systems it is implemented using
guard pages. (When there is a strict upper limit, the entire stack area is reserved in virtual memory, just not populated with RAM yet. Because virtual memory is not "free", this scheme actually uses much more total RAM than the guard page approach.) Essentially, after the stack, you have an area of memory that is allocated but inaccessible. The first access to this area causes the OS kernel to populate the guard pages (backed by RAM, so that they can be made accessible), and set up new guard pages just after. The guard page mechanism avoids the need to set up page tables for example for a gigabyte of stack; if the new guard pages cannot be set up, the kernel simply notifies the process about a segmentation violation, which typically causes the process to abort.
The problem appears when a function has more than the guard pages' worth of local variables. If the existing stack is also nearly full at the time the function is called, the first access to stack (local variables) can be beyong the guard pages, and lead to immediate segmentation violation.
I have seen this in practical code, when buffer sizes are increased, and the programmer or the code assumes that local variables do not have any practical size limits – but they do.
I do not care what the C standard says about this, because this is
practical, and as a system admin, I
needed this – be able to limit stack and heap sizes separately – to catch leaky code early enough. (Consider simulations running for days, in a distributed HPC cluster.)
For me, the C standards since C99 are like Nobel Peace Prize: a fantasy of what the world is, and an attempt to manipulate the world toward that view. It is useful, but not always practical; and we live in the practical world, not in that fantasyland.