C Multidimensional Arrays
A multidimensional array in C is an array whose elements are themselves arrays. The most common version is a two-dimensional array, which is useful for tables, matrices, game boards, image pixels, seating charts, and other row-and-column data.
Multidimensional arrays matter because they let you model structured data while still using C’s fast, contiguous array storage. The main skill is understanding how the indexes map to memory and how to pass these arrays to functions without losing the column information.
Overview: How C Multidimensional Arrays Work
A declaration such as int grid[3][4]; creates an array with 3 rows, and each row is an array of 4 integers. You can picture it as a table with 3 rows and 4 columns, but C stores it as one contiguous block of elements. There are no hidden row objects, no automatic bounds checks, and no stored length that follows the array around at runtime.
The expression grid[row][col] is evaluated in two steps. First, grid[row] chooses one row, which is an array of columns. Then [col] chooses one element inside that row. Both indexes start at 0, so for int grid[3][4], valid row indexes are 0 through 2, and valid column indexes are 0 through 3.
C stores multidimensional arrays in row-major order. That means all elements of row 0 are stored first, then all elements of row 1, then all elements of row 2, and so on. For grid[3][4], memory is laid out as grid[0][0], grid[0][1], grid[0][2], grid[0][3], then grid[1][0]. This is why loops usually put the row loop outside and the column loop inside: that visits memory in the same order it is stored.
A multidimensional array can have more than two dimensions. For example, int cube[2][3][4]; is an array of 2 layers, each layer has 3 rows, and each row has 4 columns. The same idea continues: each dimension narrows the selection until you reach one element.
One important C-specific rule appears when passing arrays to functions. A parameter written as int matrix[][4] or int matrix[3][4] is treated as a pointer to rows of 4 integers. The function does not need to know the number of rows from the type, but it must know the number of columns because that is required for address calculation. Without the column count, C cannot compute where row r begins.
Syntax
element_type name[rows][columns];
element_type name[rows][columns] = {
{row0_value0, row0_value1},
{row1_value0, row1_value1}
};
value = name[row_index][column_index];
name[row_index][column_index] = new_value;
| Part | Meaning |
|---|---|
element_type |
The type of each stored value, such as int, double, or char. |
rows |
The number of row arrays in the outer array. |
columns |
The number of elements in each row. |
row_index |
The zero-based row position. |
column_index |
The zero-based column position inside the selected row. |
Initializer braces are commonly nested by row. If there are fewer initializer values than elements, the remaining elements are initialized to zero. For readability, use one inner brace group per row even though C can also accept a flatter initializer list.
Examples
Access And Update A 2D Array
#include <stdio.h>
int main(void)
{
int scores[2][3] = {
{85, 90, 78},
{92, 88, 95}
};
printf("Row 0, column 0: %d\n", scores[0][0]);
printf("Row 1, column 2: %d\n", scores[1][2]);
scores[0][2] = 80;
printf("Updated row 0, column 2: %d\n", scores[0][2]);
return 0;
}
Output:
Row 0, column 0: 85
Row 1, column 2: 95
Updated row 0, column 2: 80
The array has 2 rows and 3 columns. The first index chooses the row, and the second index chooses the column. Updating scores[0][2] changes only the third value in the first row.
Loop Through Rows And Columns
#include <stdio.h>
int main(void)
{
int sales[3][4] = {
{12, 9, 14, 10},
{8, 15, 11, 13},
{10, 7, 16, 12}
};
size_t rows = sizeof sales / sizeof sales[0];
size_t cols = sizeof sales[0] / sizeof sales[0][0];
for (size_t row = 0; row < rows; row++) {
int row_total = 0;
for (size_t col = 0; col < cols; col++) {
row_total += sales[row][col];
}
printf("Store %zu total: %d\n", row + 1, row_total);
}
return 0;
}
Output:
Store 1 total: 45
Store 2 total: 47
Store 3 total: 45
The outer loop visits each row. For each row, the inner loop visits every column and adds the values. The sizeof calculations work here because sales is still the real array object in the same scope.
Pass A 2D Array To A Function
#include <stdio.h>
#define ROWS 3
#define COLS 3
void print_board(const char board[ROWS][COLS])
{
for (size_t row = 0; row < ROWS; row++) {
for (size_t col = 0; col < COLS; col++) {
printf("%c", board[row][col]);
if (col + 1 < COLS) {
printf(" ");
}
}
printf("\n");
}
}
int main(void)
{
const char board[ROWS][COLS] = {
{'X', 'O', 'X'},
{'O', 'X', 'O'},
{' ', ' ', 'X'}
};
print_board(board);
return 0;
}
Output:
X O X
O X O
X
The function parameter includes COLS. In a function parameter, the first dimension is not required for address calculation, but the second dimension is. The const keyword says that print_board will read the board without modifying it.
Use A Three-Dimensional Array
#include <stdio.h>
int main(void)
{
int temperatures[2][2][3] = {
{
{70, 72, 74},
{68, 71, 73}
},
{
{65, 67, 69},
{64, 66, 68}
}
};
for (size_t building = 0; building < 2; building++) {
int total = 0;
for (size_t floor = 0; floor < 2; floor++) {
for (size_t room = 0; room < 3; room++) {
total += temperatures[building][floor][room];
}
}
printf("Building %zu total: %d\n", building + 1, total);
}
return 0;
}
Output:
Building 1 total: 428
Building 2 total: 399
This example treats the dimensions as building, floor, and room. The idea is the same as a 2D array, but one more nested loop is needed to visit every value.
How It Works Step By Step
- The declaration reserves one contiguous block large enough for all elements.
- The compiler records the element type and the size of every dimension in the array type.
- For
a[i][j], C calculates the offset asi * columns + jelements from the beginning of the array. - The element offset is multiplied by the size of one element to get a byte address.
- The program reads from or writes to that address.
For int a[3][4], a[2][1] is the element at offset 2 * 4 + 1, which is offset 9 from the first integer. This calculation is why the column count is part of the type and why a function parameter must specify it.
Common Mistakes
Forgetting That Indexes Start At Zero
For int grid[3][4], the last valid element is grid[2][3], not grid[3][4]. A loop should use conditions such as row < 3 and col < 4. Using <= usually steps one element past the valid range and causes undefined behavior.
Omitting The Column Count In A Function Parameter
A function cannot correctly receive a 2D array as void f(int a[][]). The column count is required. Use a declaration such as void f(int a[][4], size_t rows), or define a named constant such as COLS and use void f(int a[][COLS], size_t rows).
Confusing A 2D Array With int **
An int matrix[3][4] is one contiguous array of arrays. It is not the same type as int **, which is a pointer to a pointer. Passing a real 2D array to a function that expects int ** is wrong because the memory layout and address calculations are different.
Using The Wrong Dimension In Nested Loops
The row count and column count are different pieces of information. If an array has 3 rows and 4 columns, the inner loop must stop before 4, not before 3. Calculate or name both dimensions to avoid mixing them up.
Best Practices
- Use clear names such as
ROWS,COLS,row, andcol. - Keep row and column counts in named constants when the size is fixed.
- Use nested loops with rows outside and columns inside for normal row-major traversal.
- Prefer nested initializer braces so each row is visually obvious.
- Pass the column count in the function parameter type, and pass the row count separately when needed.
- Use
constfor function parameters that should not modify the array. - Validate row and column indexes when they come from user input or another uncertain source.
- Do not use
int **for a normal declared 2D array; use the correct array parameter type.
Practice Exercises
- Create a
3by3integer array and print the sum of each row. - Write a function that receives a
double grades[][4]array and prints the average for each student row. - Create a small tic-tac-toe board using
char board[3][3], then print it with spaces between columns.
Summary
- A C multidimensional array is an array of arrays, most often used for row-and-column data.
- Elements are stored contiguously in row-major order.
- Both row and column indexes start at
0. - C performs no automatic bounds checking, so invalid indexes cause undefined behavior.
- When passing a 2D array to a function, the column count must be part of the parameter type.
- Nested loops are the standard way to process every element.
