Prev | Contents | Next

30 Variable-Length Arrays (VLAs)

C provides a way for you to declare an array whose size is determined at runtime. This gives you the benefits of dynamic runtime sizing like you get with malloc(), but without needing to worry about free()ing the memory after.

Now, a lot of people don’t like VLAs. They’ve been banned from the Linux kernel, for example. We’ll dig into more of that rationale later.

This is an optional feature of the language. The macro __STDC_NO_VLA__ is set to 1 if VLAs are not present. (They were mandatory in C99, and then became optional in C11.)

#if __STDC_NO_VLA__ == 1
   #error Sorry, need VLAs for this program!
#endif

But since neither GCC nor Clang bother to define this macro, you may get limited mileage from this.

Let’s dive in first with an example, and then we’ll look for the devil in the details.

30.1 The Basics

A normal array is declared with a constant size, like this:

int v[10];

But with VLAs, we can use a size determined at runtime to set the array, like this:

int n = 10;
int v[n];

Now, that looks like the same thing, and in many ways is, but this gives you the flexibility to compute the size you need, and then get an array of exactly that size.

Let’s ask the user to input the size of the array, and then store the index-times-10 in each of those array elements:

#include <stdio.h>

int main(void)
{
    int n;
    char buf[32];

    printf("Enter a number: "); fflush(stdout);
    fgets(buf, sizeof buf, stdin);
    n = strtoul(buf, NULL, 10);

    int v[n];

    for (int i = 0; i < n; i++)
        v[i] = i * 10;

    for (int i = 0; i < n; i++)
        printf("v[%d] = %d\n", i, v[i]);
}

(On line 7, I have an fflush() that should force the line to output even though I don’t have a newline at the end.)

Line 10 is where we declare the VLA—once execution gets past that line, the size of the array is set to whatever n was at that moment. The array length can’t be changed later.

You can put an expression in the brackets, as well:

int v[x * 100];

Some restrictions:

Also, entering a negative value for the size of the array invokes undefined behavior—in this universe, anyway.

30.2 sizeof and VLAs

We’re used to sizeof giving us the size in bytes of any particular object, including arrays. And VLAs are no exception.

The main difference is that sizeof on a VLA is executed at runtime, whereas on a non-variably-sized variable it is computed at compile time.

But the usage is the same.

You can even compute the number of elements in a VLA with the usual array trick:

size_t num_elems = sizeof v / sizeof v[0];

There’s a subtle and correct implication from the above line: pointer arithmetic works just like you’d expect for a regular array. So go ahead and use it to your heart’s content:

#include <stdio.h>

int main(void)
{
    int n = 5;
    int v[n];

    int *p = v;

    *(p+2) = 12;
    printf("%d\n", v[2]);  // 12

    p[3] = 34;
    printf("%d\n", v[3]);  // 34
}

Like with regular arrays, you can use parentheses with sizeof() to get the size of a would-be VLA without actually declaring one:

int x = 12;

printf("%zu\n", sizeof(int [x]));  // Prints 48 on my system

30.3 Multidimensional VLAs

You can go ahead and make all kinds of VLAs with one or more dimensions set to a variable

int w = 10;
int h = 20;

int x[h][w];
int y[5][w];
int z[10][w][20];

Again, you can navigate these just like you would a regular array.

30.4 Passing One-Dimensional VLAs to Functions

Passing single-dimensional VLAs into a function can be no different than passing a regular array in. You just go for it.

#include <stdio.h>

int sum(int count, int *v)
{
    int total = 0;

    for (int i = 0; i < count; i++)
        total += v[i];

    return total;
}

int main(void)
{
    int x[5];   // Standard array

    int a = 5;
    int y[a];   // VLA

    for (int i = 0; i < a; i++)
        x[i] = y[i] = i + 1;

    printf("%d\n", sum(5, x));
    printf("%d\n", sum(a, y));
}

But there’s a bit more to it than that. You can also let C know that the array is a specific VLA size by passing that in first and then giving that dimension in the parameter list:

int sum(int count, int v[count])
{
    // ...
}

Incidentally, there are a couple ways of listing a prototype for the above function; one of them involves an * if you don’t want to specifically name the value in the VLA. It just indicates that the type is a VLA as opposed to a regular pointer.

VLA prototypes:

void do_something(int count, int v[count]);  // With names
void do_something(int, int v[*]);            // Without names

Again, that * thing only works with the prototype—in the function itself, you’ll have to put the explicit size.

Now—let’s get multidimensional! This is where the fun begins.

30.5 Passing Multi-Dimensional VLAs to Functions

Same thing as we did with the second form of one-dimensional VLAs, above, but this time we’re passing in two dimensions and using those.

In the following example, we build a multiplication table matrix of a variable width and height, and then pass it to a function to print it out.

#include <stdio.h>

void print_matrix(int h, int w, int m[h][w])
{
    for (int row = 0; row < h; row++) {
        for (int col = 0; col < w; col++)
            printf("%2d ", m[row][col]);
        printf("\n");
    }
}

int main(void)
{
    int rows = 4;
    int cols = 7;

    int matrix[rows][cols];

    for (int row = 0; row < rows; row++)
        for (int col = 0; col < cols; col++)
            matrix[row][col] = row * col;

    print_matrix(rows, cols, matrix);
}

30.5.1 Partial Multidimensional VLAs

You can have some of the dimensions fixed and some variable. Let’s say we have a record length fixed at 5 elements, but we don’t know how many records there are.

#include <stdio.h>

void print_records(int count, int record[count][5])
{
    for (int i = 0; i < count; i++) {
        for (int j = 0; j < 5; j++)
            printf("%2d ", record[i][j]);
        printf("\n");
    }
}

int main(void)
{
    int rec_count = 3;
    int records[rec_count][5];

    // Fill with some dummy data
    for (int i = 0; i < rec_count; i++)
        for (int j = 0; j < 5; j++)
            records[i][j] = (i+1)*(j+2);

    print_records(rec_count, records);
}

30.6 Compatibility with Regular Arrays

Because VLAs are just like regular arrays in memory, it’s perfectly permissible to pass them interchangeably… as long as the dimensions match.

For example, if we have a function that specifically wants a \(3\times5\) array, we can still pass a VLA into it.

int foo(int m[5][3]) {...}

\\ ...

int w = 3, h = 5;
int matrix[h][w];

foo(matrix);   // OK!

Likewise, if you have a VLA function, you can pass a regular array into it:

int foo(int h, int w, int m[h][w]) {...}

\\ ...

int matrix[3][5];

foo(3, 5, matrix);   // OK!

Beware, though: if your dimensions mismatch, you’re going to have some undefined behavior going on, likely.

30.7 typedef and VLAs

You can typedef a VLA, but the behavior might not be as you expect.

Basically, typedef makes a new type with the values as they existed the moment the typedef was executed.

So it’s not a typedef of a VLA so much as a new fixed size array type of the dimensions at the time.

#include <stdio.h>

int main(void)
{
    int w = 10;

    typedef int goat[w];

    // goat is an array of 10 ints
    goat x;

    // Init with squares of numbers
    for (int i = 0; i < w; i++)
        x[i] = i*i;

    // Print them
    for (int i = 0; i < w; i++)
        printf("%d\n", x[i]);

    // Now let's change w...

    w = 20;

    // But goat is STILL an array of 10 ints, because that was the
    // value of w when the typedef executed.
}

So it acts like an array of fixed size.

But you still can’t use an initializer list on it.

30.8 Jumping Pitfalls

You have to watch out when using goto near VLAs because a lot of things aren’t legal.

And when you’re using longjmp() there’s a case where you could leak memory with VLAs.

But both of these things we’ll cover in their respective chapters.

30.9 General Issues

VLAs have been banned from the Linux kernel for a few reasons:

Other folks online point out that there’s no way to detect a VLA’s failure to allocate, and programs that suffered such problems would likely just crash. While fixed-size arrays also have the same issue, it’s far more likely that someone accidentally make a VLA Of Unusual Size than somehow accidentally declare a fixed-size, say, 30 megabyte array.


Prev | Contents | Next