Arrays Part 2: Multidimensional, VLA, Pointer-to-Array
This chapter covers multidimensional arrays, pointers to arrays, and variable-length arrays (VLAs). These are the more advanced array forms, and each has important portability and undefined-behavior implications.
Why This Matters
Multidimensional arrays are the natural way to represent matrices and grids. Pointers to arrays are required to pass true multidimensional arrays to functions. VLAs are a C99 feature that is optional in C11 and controversial — you must know when and whether to use them.
Prerequisites
c.core.21— arrays and decay.
Core Concept
Multidimensional arrays
A multidimensional array is an array of arrays. int m[3][4] is three arrays, each of four ints, laid out contiguously in memory.
int m[3][4]; /* 3 rows, 4 columns; 12 ints total */
m[1][2] = 5; /* row 1, column 2 */
Memory layout is row-major: the rightmost subscript varies fastest.
Pointers to arrays
int (*p)[4] is a pointer to an array of 4 ints. This is the correct type for a pointer to a row of a [N][4] array.
int m[3][4];
int (*p)[4] = m; /* m decays to &m[0], type int (*)[4] */
Note the parentheses: int (*p)[4] is a pointer to array; int *p[4] is an array of four pointers (c.ptr.array-of-ptr).
Variable-length arrays (C99)
A VLA is an array whose size is not an integer constant expression, evaluated at run time:
void f(int n)
{
int a[n]; /* VLA: size determined at run time */
}
VLAs have automatic storage and their size is computed when the declaration is reached. VLAs are optional in C11 (an implementation may or may not support them) and were made mandatory again in C23 in a different form. STANDARD-VERSION-DEPENDENT
Syntax
Multidimensional arrays
int m[3][4];
int m[3][4] = { {1,2,3,4}, {5,6,7,8}, {9,10,11,12} };
int m[][4] = { {1,2,3,4}, {5,6,7,8} }; /* first dimension inferred */
Pointer to array
int (*p)[4];
VLA
int a[n]; /* VLA (automatic) */
int b[n][m]; /* VLA with multiple dimensions */
Examples
Passing a true multidimensional array
#include <stdio.h>
void print_matrix(int rows, int cols, int m[rows][cols])
{
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++)
printf("%d ", m[i][j]);
printf("\n");
}
}
int main(void)
{
int m[2][3] = { {1,2,3}, {4,5,6} };
print_matrix(2, 3, m);
return 0;
}
Here m[rows][cols] is a VLA parameter (the dimensions are parameters), and the compiler can index it correctly.
Pointer to array vs. array of pointers
int (*p)[4]; /* pointer to array of 4 ints */
int *q[4]; /* array of 4 pointers to int */
p + 1 moves by 4 * sizeof(int) bytes; q + 1 moves by sizeof(int *) bytes.
How It Works
A multidimensional array is contiguous; m[i][j] computes the offset i * cols + j (row-major) and indexes that element. A pointer to an array carries the inner dimension in its type, so the compiler can compute the row stride correctly. A VLA's size is materialized at run time and stored implicitly so sizeof and indexing work.
Variations
Array of pointers (ragged arrays)
int *rows[3]; /* each element can point to a different-length array */
This is a "ragged" structure, distinct from a contiguous multidimensional array. Use it when rows have different lengths.
VLA typedefs
typedef int VLA[n]; /* VLA typedef (run-time size) */
Common Mistakes
- Writing
int *p[4]when you meanint (*p)[4]. - Passing a
[N][M]array to a parameter declaredint **(they are not the
same; a [N][M] decays to int (*)[M], not int **).
- Assuming all compilers support VLAs (they are optional in C11).
- Forgetting that VLA lifetime begins at the declaration point.
Undefined Behavior
- Indexing a multidimensional array out of bounds.
VERIFIED - A VLA whose size is zero or negative is undefined behavior (and a constraint
violation for non-positive size in some cases).
- Jumping with
gotointo the scope of a VLA is a constraint violation/UB.
Portability
- VLAs are mandatory in C99, optional in C11/C17, and reworked in C23. They are
supported by GCC and Clang but not by MSVC. STANDARD-VERSION-DEPENDENT
- The exact size of pointers affects pointer-to-array stride but not the rules.
Under the Hood
A pointer to an array encodes the inner dimension, letting the compiler emit the correct scaled addressing for p + 1. VLA sizes are typically stored in a hidden local (or on the stack) so sizeof and cleanup work correctly.
Practical Usage
- Use true multidimensional arrays for fixed grids/matrices.
- Use pointer-to-array parameters to pass matrices with known inner dimension.
- Avoid VLAs for large or untrusted sizes (stack overflow risk); use heap
allocation instead.
Exercises
1. Write a function that takes int (*m)[4] and prints each element of a [N][4] array, and call it. 2. Explain why int ** is not interchangeable with int (*)[4]. 3. Write a program using a VLA and another using malloc for the same task; compare them. 4. Demonstrate that sizeof on a VLA is a run-time value.
Deep Challenge
Explain the memory layout of int m[2][3] (row-major), and write a function that flattens it into a 1D array using explicit index arithmetic. Then contrast this with a ragged array of pointers and explain the performance and layout trade-offs.
Related Concepts
c.ptr.array-of-ptr— arrays of pointers.c.struct.fam— flexible array members.c.mem.malloc— heap allocation vs. VLA.
References
- ISO/IEC 9899:2018 §6.5.2.1 (subscripting), §6.7.6.2 (array declarators),
§6.7.6.3 (function declarators), §6.2.5 (array types).
Verification
- Row-major layout.
VERIFIED [N][M]decays toint (*)[M], notint **.VERIFIED- VLA optionality in C11.
VERIFIED - No example was executed during generation unless noted. `Execution not
verified.`
Progress
- [ ] Read
- [ ] Understand
- [ ] Complete examples
- [ ] Complete exercises
- [ ] Complete deep challenge
Concept checkboxes
- [ ] Multidimensional arrays
- [ ] Row-major layout
- [ ] Pointers to arrays
- [ ] VLA
- [ ] VLA portability
Mastery levels
| Concept | Current level (0–8) | Target level |
|---|---|---|
| c.arr.multi | 0 | 5 |
| c.arr.vla | 0 | 5 |
| c.arr.ptr-to-array | 0 | 6 |