Obscure C99 Array Features
Tags: c
Published: 2022-01-14 21:27:41

## Introduction

[C99](https://en.wikipedia.org/wiki/C99) introduced a number of new features for arrays.  Even though C99 is over 20 years old, you seldom see these new features used in the wild.  Because of that, you’re less likely to be familiar with them and so less likely to use them in your own code (but maybe you shouldn’t!). So here’s a tour of those features.

## Flexible Array Members

Introduced in C99, the last member of a `struct` with more than one named member may be a _flexible array member_, that is an array of an unspecified size:
```c
struct s {
    size_t n;
    double d[];            // Flexible Array Member
};
```
Typically, such a `struct` serves as a “header” for a larger region of memory, perhaps containing a binary file read from disk.

Note that it’s up to your code to somehow remember how big the array is.  (This can, of course, be stored in a member that precedes the array in the `struct`.)

When `sizeof` is applied to such a `struct`, it’s as if the array isn’t there — except there _may_ be some additional padding.

While you can have such a `struct` on the stack, it’s not useful since no size is set aside for the array (hence, accessing the array is [undefined behavior](https://dev.to/pauljlucas/undefined-behavior-in-c-and-c-3a20)).  To be useful, such a `struct` has to be allocated on the heap: it’s then when the size is specified:
```c
struct s *ps = malloc( sizeof(struct s) + sizeof(double[n]) );
```
Lastly, note that assignments among such `struct`s do _not_ copy the array (because the compiler has no idea how big it is):
```c
struct s *ps1, *ps2;
// ...
*ps1 = *ps2;               // copies only 'n' member
```
Incidentally, C++ never adopted flexible array members from C99.

## Variable Length Arrays

Prior to C99, all arrays had to be declared to be of a fixed length (known at compile-time).  Introduced in C99, _variable length arrays_ (VLAs, not to be confused with flexible array members) can be declared to be of a _variable_ length (not known until run-time). For example:

```c
void f( size_t n ) {
    int a[n];
```

Not only that, but the `sizeof` operator, historically a compile-time operator, is now _sometimes_ a run-time operator — when its argument is a VLA:
```c
size_t sz = sizeof(a) / sizeof(a[0]); // sz = 10
```
(The first `sizeof` is evaluated at run-time because its argument is a VLA; the second `sizeof` is still evaluated at compile-time.)

### Variable Length Arrays Caveat

One serious caveat to VLAs is that, if the length is too big, it will _silently_ overflow the stack. Additionally, unlike `malloc()` returning `NULL` upon failure, there's _no way_ to detect when a VLA overflows the stack. Hence, if the size can be “too big,” your code has to guard against it:
```c
size_t const A_LEN_MAX = 1024;
// ...

if ( n > A_LEN_MAX )
    // Do something else?
int a[n];
```
However, if you know that `A_LEN_MAX` is the maximum safe size, then you might as well just declare `a` to be of that size and not use a VLA.

Incidentally, you could do small size optimization:
```c
void f( size_t n ) {
    int a[A_LEN_MAX];
    int *const p = n <= A_LEN_MAX ? a : malloc( n * sizeof(*p) );
    // use only 'p' to access array
    if ( p != a )
        free( p );
}
```
That is, if `n` isn’t too big, use the (fixed sized) array on the stack; otherwise, use a dynamically sized array in the heap.  This has the advantage of saving on the calls to `malloc()` and `free()` for “small” `n` yet still works for “large” `n`. But notice that a VLA is _not_ being used.

Hence, the moral is: use VLAs _only_ when you don’t know the size at compile time but can _guarantee_ that it won’t be “too big.” However, this is pretty much never true.

In hindsight, VLAs, though they seem convenient at times, are problematic, so much so that C11 made VLAs an _optional_ feature. Incidentally, C++ never adopted VLAs from C99.

## Array Syntax for Parameters

As you should be aware, array syntax can be used to declare function parameters, but, as you should also be aware, it’s just [syntactic sugar](https://en.wikipedia.org/wiki/Syntactic_sugar) since the compiler rewrites such parameters as pointers:
```c
void f( int a[] );         // int *a
```
> Note that I’m intentionally writing “array syntax for parameters” and _not_ “array parameters” because “array parameters,” despite appearances, simply don’t exist in C.

The only potential benefit of using array syntax for parameters is that it conveys to the human reader that `a` is presumed to be a pointer to _at least one_ `int` rather than a _exactly one_ `int`.  However, it’s _only_ a presumption and _not_ a guarantee since you can call such a function with a null pointer:
```c
f( NULL );                 // f’s 'a' will be NULL
```
Note that adding a size doesn’t help:
```c
void f( int a[10] );       // int *a
```
While again this might convey to a human reader that `a` is presumed to be an array of 10 `int`s, the compiler _ignores_ the size.

> Array syntax for parameters in C is a remnant of how pointers are declared in New B (the precursor to C). See [_The Development of the C Language_](https://www.bell-labs.com/usr/dmr/www/chist.html), Dennis M. Ritchie, April, 1993.

### Non-Null Array Syntax Pointers for Parameters

One of the features added in C99 was the ability to declare an “array” function parameter that must not be null and be of a minimum size:
```c
void f( int a[static 10] );
```
If you try to pass either `NULL` or an array that has fewer than 10 `int`s, the compiler will warn you.

> This marks yet another overloading of the `static` keyword in C since this `static` has nothing to do with either linkage or duration. Incidentally, C++ never adopted this syntax.
> Also incidentally, C99 did _not_ introduce a parallel way to specify that a _pointer_ parameter must not be null.

### Array Syntax for Parameters Caveat

Using array syntax for function parameters can also be [dangerous](https://lkml.org/lkml/2015/9/3/428):
```c
void f( int a[10] ) {      // int *a
    for ( size_t i = 0; i < sizeof(a)/sizeof(*a); ++i ) {
        // ...
```
The intention is to iterate over all the elements of the array, but, despite the `sizeof` expression being correct for an array, `a` is, again, _not_ an array, but a pointer; so you’ll get the size of a _pointer_ divided by the size of an `int`.  Fortunately, `gcc` will warn about this.

### Qualified Array Syntax for Parameters

To drive home that parameters declared with array syntax really are pointers, you _can change_ them:
```c
int ra[10];                // real array

void f( int pa[] ) {       // int *pa
    ++ra;                  // error (as expected)
    ++pa;                  // OK (surprisingly)
```

C99 also added the ability to qualify the rewritten pointer:
```c
void f( int pa[const] ) {  // int *const pa
    ++pa;                  // error now
```

> In addition to `const`, you can also qualify the pointer with [`volatile`](https://dev.to/pauljlucas/what-volatile-does-in-c-and-c-5147) and `restrict` (CVR).

Note that neither of these:
```c
void f( int const pa[] );  // pointer to const int
void f( const int pa[] );  // same as above
```
is the same thing: the `const` _outside_ the `[]` refers to the `int` and not `pa`.

> Why doesn’t the compiler convert parameters with array syntax to `const` pointers? Because `const` wasn’t a part of C when Ritchie invented it.
> Incidentally, C++ never adopted this syntax.

### Variable Length Array Syntax for Parameters

C99 also added the ability to use VLAs for function parameters:
```c
void f( size_t n, int a[n] ) { // int *a
```
That is, the size of the “array” is given by an integral parameter that precedes it.  Note, however, that `a` is _still_ a pointer.  Despite having the size information at run-time, `sizeof(a)` will _still_ return the size of the pointer. Hence, this “feature” serves only to convey to the human reader that `n` is the presumed size of the “array.”

However, this “feature” is actually useful for multidimensional arrays.  But before we get to that, a quick refresher on multidimensional array syntax for function parameters.

### Multidimensional Array Syntax for Parameters

As you should be aware, array syntax can also be used to declare function parameters for multidimensional “arrays”:
```c
void f( int a[10][20] );   // int (*a)[20]
```
The rule that the compiler converts array syntax for a function parameter into a pointer happens _only_ for the first (left-most) dimension; the remaining dimension(s) keep their “array-ness.”  Hence, `a` is a pointer to a real array of 20 `int`s.

> Note that the parentheses are necessary: without them, it would be an array of 20 pointers to `int`. FYI: to help decipher cryptic C declarations, you can use [cdecl](https://github.com/paul-j-lucas/cdecl).

Pointers to array don’t often occur in C programs since the name of an array “decays” into a pointer to its first element. In most cases, this is good enough even though the size information is lost. However, a pointer to an array _retains_ the array’s size as part of the type, so assignments between pointers to arrays of different size are warned about:
```c
int (*p3)[3];              // pointer to array 3 of int
int (*p5)[5];              // pointer to array 5 of int
p5 = p3;                   // warning: incompatible pointers
```
In particular, given:
```c
int a[10];
int *pi = a;               // pointer to int (via decay)
int (*pa)[10] = &a;        // pointer to array 10 of int
```
both `pi` and `pa` point to the same location in memory (here, `&a[0]`), but a “pointer to array” is an entirely different thing from a pointer that results from array decay.  For `pi`, the compiler “forgets” the size of the array to which it points; for `pa`, it “remembers” the size.

Part of the reason pointers to array aren’t used much is because it’s clunky to access array elements since you have to dereference the pointer first:
```c
int e1 = (*p3)[1];         // must dereference p3 first
```
However, you can dereference the pointer _once_ into another pointer then use _that_ pointer:
```c
int *p = *p3;
int e1 = p[1];             // same as: (*p3)[1]
```

### Multidimensional VLA Parameters

Multidimensional array syntax for parameters can be used for VLAs:
```c
void f( size_t m, size_t n, int a[m][n] ) {
```
Since the compiler rewrites _only_ the _first_ array dimension as a pointer, the above is really:
```c
void f( size_t m, size_t n, int (*a)[n] ) {
```
that is `a` is a pointer to a VLA of `n` `int`s.  In this case, the VLA is actually a useful feature since the `n` allows the compiler to know the length of each row of the array.  Additionally, `sizeof` (the first one below) once again becomes a run-time operator:
```c
size_t sz = sizeof(*a) / sizeof(**a); // sz = n
```

Unlike VLAs in general, VLAs used for function parameters are _safe_ since the actual arrays passed _to_ the function can be (and often are) normal arrays:
```c
void f( size_t m, size_t n, int a[m][n] ) {
    // ...
}

void g( void ) {
    int a[10][20];
    f( 10, 20, a );
}
```
There’s no _new_ VLA being created at run-time here, so it can’t overflow the stack.

### Multidimensional VLA Parameter Declarations

When _declaring_ (as opposed to defining) functions, C allows you to omit the parameter names; however, if you do that, then there’s no name to specify the size of a VLA; but C99 added a new syntax for this case:
```c
void f( size_t, size_t, int[][] );    // error
void f( size_t, size_t, int[*][*] );  // OK
```
That is, you use `*` to denote a VLA of an unnamed size. Note that since the first array dimension is always converted to a pointer, the `*` is needed only starting with the _second_ dimension:
```c
void f( size_t, size_t, int[][*] );   // same as previous
```
Hence, you never need `*` when using single dimension array syntax.

## Conclusion

C99 added the new array features of:

1. Flexible Array Members.
2. VLAs (which are unsafe, so you probably shouldn’t use them).
3. VLAs for function parameters (which are safe, but really only useful for multidimensional arrays).
4. `static` that requires non-null, minimum-sized arrays be passed for parameters.
5. The ability to `const`, `volatile`, or `restrict` qualify the rewritten pointers that decayed from arrays for function arguments.