3.5. Variable Length Arrays

Note:: Variable length arrays are not supported in Cray C++.

In extended mode, you can declare variable length arrays. A variable length array (VLA) is an array that has a size (at least one dimension) that is determined at run time. The ability to use variable length arrays enhances the compiler's range of use for numerical programming.

3.5.1. Declarator Restrictions

An array type with a size specifier (dimension) that must be evaluated at program execution time is said to be a variable length array type.

Use of this variable type is restricted to only block and function prototype scopes. An object type that contains one or more derived declarator types of variable length array type is said to be variably modified. For example, a “pointer to a VLA” is a pointer (not a VLA) but it is still variably modified. Members of structure and union types are not allowed to have variably modified type.

Function parameters can be declared with variable length array type. For example, the following matrix_mult function declaration can be used to define a function that performs a matrix multiply of an n by m and an m by n matrix to yield an n by n result:

void matrix_mult(int n, int m, double a[n][n],
                 double b[n][m], double c[m][n]);

In the previous example, the array sizes are computed each time the function is called. In addition, the conventional workaround of maintaining a table of pointers to each of the rows in the matrices is avoided because variable array addressing is done automatically.

C variable length arrays are similar to Fortran automatic and adjustable arrays. Variable length arrays can also be used as local (auto) variables. Previously, a call to the malloc(3) library function was required to allocate such dynamic arrays, and a call to the free(3) library function was required to deallocate them.

3.5.2. Variable Length Array (VLA) Declarators

The size of a variable length array (VLA) dimension must be of integer type and is not a constant expression. The only storage class specifiers that can be explicitly given are auto (for block scope arrays) and register (for parameter array declarators). Using an expression that produces side effects (for example, function calls) to specify the size of an array is permitted, however, the order of evaluation of two or more such expressions within a single declaration is undefined.

A variable length array declaration cannot have an initializer.

For two array types to be compatible, both must have compatible element types, and if both size specifiers are present and are integer constant expressions, then both sizes must have the same value. A VLA is always compatible with another array type if they both have the same element type. If the two array types are used in a context that requires them to be compatible, it is undefined behavior if the dimension sizes are unequal at run time.

The following example illustrates arrays that are incompatible and arrays that are compatible but have undefined results unless certain criteria are met:

extern int n;
extern int m;
void func(void)
{
  int a[n][6][m];
  int (*p)[4][n];        /* pointer to VLA */
  int c[n][n][6][m];
  int (*r)[n][n][n+1];   /* pointer to VLA */

  p = a;/* error - not compatible because 4 != 6 */

  r = c; /* compatible - but undefined behavior
            unless n==6 and m==n+1 */
}

The following example illustrates arrays that are compatible, but have undefined behavior at execution time:

int dim4 = 4;
int dim6 = 6;
main()
{
  int (*q)[dim4][8][dim6]; /* pointer to VLA */
  int (*r)[dim6][8][1];    /* pointer to VLA */

  r = q; /* compatible, but undefined behavior at
            execution time */
}

3.5.3. Function Declarators and Variable Length Arrays

For each parameter declared with variable length array type, the type used for compatibility comparisons is the one that results from conversion to a pointer type, as for fixed length arrays.

All identifiers used in VLA size expressions must be declared prior to use (as is the case with the usage of all variables). Thus, the order in which function parameters are declared is important when VLAs are used. For example:


void f1(int n1, double a1[n1]) {} /* Correct */

void f2(int a2[n2], int n2) {}    /* Error: n2 not declared before VLA */

int n3;
void f3(int a3[n3], int n3) {}    /* Correct, but file scope n3 is used in VLA
                                     size expression */

The manner in which a function declaration with variably modified parameters is handled depends on whether the function definition is found, as follows:

3.5.4. Variable Length Array Type Definitions

Type definition declarations (that is, typedef names) that specify a variable length array (VLA) type must have block scope. The size of the VLA is determined at the time that the type definition is declared and not at the time it is invoked for use in an actual declarator.

The following example shows the use of a VLA type definition:

void func(int n)
{
     typedef int A[n]; /* Correct; declared in block scope */
     A a;
     A *p = &a;
}

The following example shows an invalid VLA type definition:

int n;
typedef int A[n]; /* not valid; declared in file scope */

The following example declares VLAs at different scopes:

void func(int n)
{
     typedef int A[n]; /* A is n ints; current value of n */
     n += 1;
     {
        A a; /* a is n ints; n without += 1 */
        int b[n];/* a and b are different sizes  */
        for (i = 1; i < n; i++)
           a[i-1] = b[i];
     }
}

3.5.5. sizeof Operator and Variable Length Arrays

When the sizeof operator is applied to an operand that has variable length array type, the result is not a constant expression (unlike other sizeof expressions) and is computed at program execution time. For example:

int func(int n) {
    char b[n];
    return sizeof(b);
}

main() {
    printf("%d\n", func(10));
} 

The output from the preceding example is 10.

3.5.6. goto Statements

Use of a goto statement to jump into a block scope level where a variably qualified object has been declared is not allowed. For example:

void func(int n)
{
     int j = 4;
     goto lab3; /* error - going INTO scope of VLA */
     {
        double a[n];
        a[j] = 4.4;
lab3:
        a[j] = 3.3;
        goto lab4; /* OK - going WITHIN scope of VLA */
        a[j] = 5.5;
lab4:
        a[j] = 6.6;
     }
     goto lab4; /* error - going INTO scope of VLA */
     return;
  }

3.5.7. switch Statement

When using the switch statement, the controlling expression must not cause control to jump into a block where a variably qualified object has been declared.

int i, j = 30;
main() {
   int n = 10;
   int m = 20;
   switch (n) {
     int a[n]; /*error; switch bypasses declaration of a[n] */
     case 10:
        a[0] = 1;
        break
     case 20:
        a[0] = 2;
        break;
   }
   switch (i) {
     case 0:
        {
           int b[n]; /*OK; declaration of b[n] not bypassed */
           b[2] = 4;
        }
        break;
     case 1:
        break;
   }
}

3.5.8. setjmp and longjmp Functions

If the longjmp function returns control back to the point of the setjmp invocation, the memory associated with a variable length array object could be squandered.

In the following example, the function h causes storage to be lost for the variable length array object a which is declared in function g, because its existence is unknown to h. Additional storage would be lost for variable length array object b, which is declared in function h.

#include <setjmp.h>
void g(int n);
void h(int n);
int n = 6;
jmp_buf buf;

void f(void) {
  int x[n];
  setjmp(buf);
  g(n);
}

void g(int n) {
  int a[n];
  h(n);
}

void h(int n) {
  int b[n];
  longjmp(buf,2);
}