Why is this VLA (variable-length array) definition unreliable?

Diagnosis

In the code in the question, the variable n is uninitialized when it is used in the definition of vla. Indeed, with GCC set fussy, the code shown produces a compilation error (it’ll give a warning if you are careless enough to omit -Werror from your compilation options — don’t do that!):

$ gcc -std=c11 -O3 -g -Wall -Wextra -Werror -Wstrict-prototypes -Wmissing-prototypes -Wshadow -pedantic-errors  vla37.c -o vla37  
vla37.c: In function ‘main’:
vla37.c:6:5: error: ‘n’ is used uninitialized [-Werror=uninitialized]
    6 |     double vla[n];
      |     ^~~~~~
vla37.c:5:9: note: ‘n’ declared here
    5 |     int n;
      |         ^
cc1: all warnings being treated as errors
$

(That’s from GCC 11.2.0 on a machine running RedHat RHEL 7.4.)

The trouble is that the compiler must know the size of the array when it is declared, but the value in n is undefined (indeterminate) because it is uninitialized. It could be huge; it could be zero; it could be negative.

Prescription

The cure for the problem is simple — make sure the size is known and sane before it is used to declare the VLA:

#include <stdio.h>

int main(void)
{
    int n;

    if (scanf("%d", &n) != 1)
        return 1;

    double vla[n];
    for (int i = 0; i < n; i++)
    {
        if (scanf("%lf", &vla[i]) != 1)
            return 1;
    }
    for (int i = 0; i < n; i++)
        printf("[%d] = %.2f\n", i, vla[i]);
    return 0;
}

Now you can run the result:

$ vla41 <<<'9 2.34 3.45 6.12 8.12 99.60 -12.31 1 2 3'
[0] = 2.34
[1] = 3.45
[2] = 6.12
[3] = 8.12
[4] = 99.60
[5] = -12.31
[6] = 1.00
[7] = 2.00
[8] = 3.00
$

(That assumes your shell is Bash or compatible with Bash and supports ‘here strings’ (the <<<'…' notation.)

The code shown in the question and in this answer is barely adequate in handling I/O errors; it detects input problems but doesn’t provide useful feedback to the user.
The code shown does not validate the value of n for plausibility. You should ensure that the size is larger than zero and less than some upper bound. The maximum size depends on the size of the data being stored in the VLA and the platform you’re on.

If you’re on a Unix-like machine, you probably have 8 MiB of stack; if you’re on a Windows machine, you probably have 1 MiB of stack; if you’re on an embedded system, you may have much less stack available to you. You need to leave some stack space for other code too, so you should probably check that the array size is not more than, for sake of discussion, 1024 — that would be 8 KiB of stack for an array of double, which is not huge at all but it provides plenty of space for most homework programs. Tweak the number larger to suit your purposes, but when the number grows, you should use malloc() et al to dynamically allocate the array instead of using an on-stack VLA. For example, on a Windows machine, if you use a VLA of type int, setting the size above 262,144 (256 * 1024) almost guarantees that your program will crash, and it may crash at somewhat smaller sizes than that.

Lessons to learn

  • Compile with stringent warning options.
  • Compile with -Werror or its equivalent so warnings are treated as errors.
  • Make sure the variable defining the size of a VLA is initialized before defining the array.
    • Not too small (not zero, not negative).
    • Not too big (not using more than 1 megabyte on Windows, 8 megabytes on Unix).
    • Leave a decent margin for other code to use as well.

Note that all compilers that support VLAs also support variables defined at arbitrary points within a function. Both features were added in C99. VLAs were made optional in C11 — and a compiler should define __STDC_NO_VLA__ if it does not support VLAs at all but claims conformance with C11 or later.

C++ and variable-length arrays

Standard C++ does not support C-style VLAs. However, GCC (g++) does support them by default as an extension. This can cause confusion.

Leave a Comment