Multidimensional arrays and address calculation
Memory is one long line, so a 2-D or 3-D array has to be flattened. Learn row-major and column-major address formulas (with arbitrary bounds), how C declares and passes 2-D arrays, and the index formulas for triangular and tridiagonal matrices that GATE keeps asking for.
Why it exists
A table of numbers, a matrix, an image: data with rows and columns is everywhere, and C offers int m[3][4] for it. But memory has no rows and no columns. It is a single line of numbered bytes. The compiler therefore has to flatten the table into that line, and every access m[i][j] becomes an arithmetic formula on the starting address. GATE asks for exactly this formula, in many dressings: different bounds, row-major or column-major, three dimensions, or only half of a matrix stored.
Prerequisites
Precise definitions
- Two-dimensional array. In C,
int m[3][4]is an array of 3 elements, each of which is an array of 4 ints. There are ints in one contiguous block. There is no separate "row structure":m[i]is simply the -th inner array. - Row-major order. The elements are stored row after row: the whole of row 0, then row 1, then row 2. C always uses row-major order. Consecutive elements of one row are adjacent; consecutive elements of one column are a whole row apart.
- Column-major order. Column after column. Fortran and MATLAB use it. GATE questions state which one applies when they leave C.
- Address formulas. Let the base address be and the element size .
For an array with rows and columns, 0-based indices, row-major:
For general bounds, rows and columns , let be the number of rows and the number of columns:
For three dimensions a[D1][D2][D3] (0-based, row-major):
The last index varies fastest. The pattern: the number you multiply by is always the product of the sizes of the dimensions to the right.
- What the types are. For
int m[3][4]:
| Expression | Type | Size | Meaning |
|---|---|---|---|
m | int [3][4] (decays to int (*)[4]) | 48 | the whole table |
m[1] | int [4] (decays to int *) | 16 | row 1 |
m[1][2] | int | 4 | one element |
m + 1 | int (*)[4] | 8 (64-bit) | moves one whole row (16 bytes) |
*(m + 1) | int [4] | 16 | row 1 again, so m[1] is *(m + 1) |
*(*(m + i) + j) | int | 4 | the same as m[i][j] |
Rows: sizeof m / sizeof m[0] . Columns: sizeof m[0] / sizeof m[0][0] .
- Pointer to an array versus array of pointers.
int (*p)[4]is one pointer to an array of 4 ints (this is whatmdecays to).int *q[4]is an array of 4 pointers to int. The parentheses decide. with 4-byteintand 8-byte pointers,sizeof *pis 16 andsizeof qis 32. - Declaring and passing. Every dimension except the first must be given, because the compiler needs the row length for the formula above.
void f(int a[][4], int rows)is the same asvoid f(int (*a)[4], int rows). A parameterint a[][]is an error. - Initialisation. Braces can be nested to give each row. Missing values are zero. If the braces are flattened, values fill the array in row-major order, and only the first dimension may be left out:
int b[][3] = {1, 2, 3, 4, 5};makes 2 rows,{1, 2, 3}and{4, 5, 0}.
How it works
The run below is real. It declares m[3][4] but does not initialise it, so the cells show ?. Look at the addresses: they climb by 4 along a row and then keep climbing into the next row with no gap. The grid you see is only a way of drawing 12 consecutive ints. The last two steps take a pointer to row 2 (m[2], which begins cells after the start) and move one cell along it: m[2][1] sits cells after the start.
A 3 by 4 array is 12 ints in row-major order
Real run, real addressesC source
Stack
mint
Amber = changed in this step. Hover a cell for its full address. Arrows show where a pointer points; the small tag under a cell names the pointers aimed at it.
Step 1
m has 3 rows of 4 ints: 12 cells in one block. It is uninitialised, so the cells show ?. Look at the addresses: they increase by 4 along a row and then continue straight into the next row.
Triangular and tridiagonal matrices
A symmetric, triangular or tridiagonal matrix has many entries that are known to be 0 (or repeated). Instead of cells, only the useful entries are stored, row by row, in a 1-D array, and an index formula converts to a position.
Lower triangular (entries with ), stored row-wise, 0-based. Rows 0, 1, ..., hold entries before row , and is the -th entry in its row:
With 1-based indices and 1-based positions, the formula is , so the address is .
Upper triangular (entries with ), stored row-wise, 0-based. Row holds entries, so before row there are entries:
Tridiagonal (entries with ), stored row-wise, 0-based. Row 0 has 2 entries, every other row 3 (except the last, which has 2). The formula collapses to
The lower-triangular and tridiagonal formulas are checked by programs in the worked examples (the program lists the entries in storage order and compares the position it counts with the formula). The upper-triangular, general-bounds and column-major formulas were checked by a one-off script during the build.
Worked examples
EX 1Sizes, rows, columns and one offset
Easy#include <stdio.h>
int main(void) {
int m[3][4];
printf("%zu %zu %zu %zu %zu %td\n", sizeof m, sizeof m[0], sizeof m[0][0],
sizeof m / sizeof m[0], sizeof m[0] / sizeof m[0][0],
(char *)&m[1][2] - (char *)&m[0][0]);
return 0;
}Output (4-byte int):
48 16 4 3 4 24Reading it. The whole table is 48 bytes, a row is 16, an element is 4. Dividing gives 3 rows and 4 columns. m[1][2] is bytes after the first element, the same formula as in the animation.
EX 2Pointer forms of m[i][j], and pointer to a row
Medium#include <stdio.h>
int main(void) {
int m[3][4];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; j++) m[i][j] = i * 4 + j;
int (*pa)[4] = m;
printf("%d %td %d %zu %zu\n", *(*(m + 2) + 1), (char *)(pa + 1) - (char *)pa,
pa[1][3], sizeof *pa, sizeof pa);
return 0;
}Output (64-bit):
9 16 7 16 8Reading it. *(*(m + 2) + 1): m + 2 is row 2, *(m + 2) is that row decayed to a pointer to its first int, + 1 moves one int, and the outer * reads it: . pa is a pointer to a row of 4 ints, so pa + 1 moves 16 bytes. pa[1][3] is . sizeof *pa is the size of the row (16), sizeof pa the size of one pointer (8).
EX 3Row-major and column-major with arbitrary bounds
GATE levelAn array A[1..5][1..4] has 2-byte elements and base address 1000. Find the address of A[3][2] in row-major and in column-major order.
Dimensions. rows, columns.
Row-major.
Column-major.
Check by counting. Row-major: rows 1 and 2 hold elements, then A[3][1] is the 9th, and A[3][2] is the 10th, so 9 elements come before it. Column-major: column 1 holds 5 elements, then A[1][2], A[2][2] come before A[3][2], so 7 elements come before it. Both match.
EX 4Initialisers, partial rows and the omitted first dimension
Medium#include <stdio.h>
int main(void) {
int a[2][3] = {{1}, {4, 5}};
int b[][3] = {1, 2, 3, 4, 5};
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) printf("%d ", a[i][j]);
printf("| ");
}
printf("\n%zu:", sizeof b / sizeof b[0]);
for (int i = 0; i < 2; i++)
for (int j = 0; j < 3; j++) printf(" %d", b[i][j]);
printf("\n");
return 0;
}Output:
1 0 0 | 4 5 0 |
2: 1 2 3 4 5 0Reading it. Each inner brace initialises one row, and anything not given is zero. In b the values are not grouped, so they fill row 0 completely and then row 1, and the compiler counts that 5 values need 2 rows of 3 cells. The unused last cell is zero. This is legal C, but gcc's -Wall warns about the missing braces, because grouping each row in braces is clearer.
EX 5Three dimensions
GATE level#include <stdio.h>
int main(void) {
int a[2][3][4];
printf("%td %zu\n", (char *)&a[1][2][3] - (char *)&a[0][0][0], sizeof a);
return 0;
}Output (4-byte int):
92 96Check with the formula. . If the base address is 5000, the address is 5092. The full array has ints, 96 bytes, so a[1][2][3] is the very last element, at offset .
EX 6Lower triangular matrix stored row by row
GATE level#include <stdio.h>
int main(void) {
int n = 5, idx = 0, found = -1;
for (int i = 0; i < n; i++)
for (int j = 0; j <= i; j++) {
if (i == 3 && j == 2) found = idx;
idx++;
}
printf("%d %d %d\n", found, 3 * 4 / 2 + 2, idx);
return 0;
}Output:
8 8 15Reading it. The loops list the stored entries in storage order (row 0 has 1, row 1 has 2, ...). Entry turns out to be at position 8, and the formula agrees. In total entries are stored instead of 25.
With 1-based indices. The same physical entry is called when rows and columns are numbered from 1. The 1-based formula gives position (counting from 1), which is position 8 when counting from 0, and the address is . The two conventions describe the same cell.
EX 7Tridiagonal matrix: index 2i + j
GATE level#include <stdio.h>
int main(void) {
int n = 6, idx = 0, ok = 1;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (j >= i - 1 && j <= i + 1) {
if (idx != 2 * i + j) ok = 0;
idx++;
}
printf("%d %d %d\n", ok, idx, 3 * n - 2);
return 0;
}Output:
1 16 16Reading it. The loop visits the entries with in row-major order and counts them. For every one of them, the running count equals (ok stays 1), and the total is . So to find the address of in a tridiagonal matrix stored row-wise, use (0-based).
EX 8Passing a 2-D array to a function
Medium#include <stdio.h>
int sum(int a[][4], int rows) {
int s = 0;
printf("%zu %zu ", sizeof a, sizeof a[0]);
for (int i = 0; i < rows; i++)
for (int j = 0; j < 4; j++) s += a[i][j];
return s;
}
int main(void) {
int m[3][4];
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; j++) m[i][j] = i * 4 + j;
printf("%d\n", sum(m, 3));
return 0;
}Output (64-bit):
8 16 66Reading it. int a[][4] is really int (*a)[4], a pointer to a row of 4 ints, so sizeof a is 8 (one pointer) and sizeof a[0] is 16 (one row). The function cannot know the number of rows: that is why rows is passed. The column count 4 is part of the type and is what lets a[i][j] compute . The sum is . (gcc warns about sizeof a, which is the point of this trap.)
Cheat box
Cheat box
int m[R][C]rows of ints. cells, contiguous, row-major in C.- Row-major, 0-based: . Bounds , : .
- Column-major: .
- 3-D: . Multiply by the sizes to the right.
m + 1moves a row.m[i]is a row.*(*(m + i) + j)ism[i][j].sizeof m[0]is a row.- Parameter
int a[][C]isint (*a)[C]. Only the first dimension may be omitted. - Lower triangular, row-wise, 0-based: ( cells). Upper: . Tridiagonal: ( cells).
int (*p)[4]: pointer to a row.int *q[4]: array of 4 pointers.
Traps and GATE patterns
Connections
Builds on
Across subjects
The row-major formula is the one-dimensional array formula applied twice. The type rules rely on pointer arithmetic and on the way an array name decays. The reason loops that run along rows are faster than loops that run down columns is the cache, which is the subject of cache mapping: in row-major order the elements of a row are neighbours in memory. Passing a 2-D array to a function is a special case of parameter passing.
PYQs
5 previous year questions from your book. I re-solved each one myself (5 by running or simulating, 0 by reasoning only); the book's key is shown next to my answer. All PYQs
Consider the following ANSI C program.
#include <stdio.h>
int main() {
int arr[4][5];
int i, j;
for (i = 0; i < 4; i++) {
for (j = 0; j < 5; j++) {
arr[i][j] = 10 * i + j;
}
}
printf("%d", *(arr[1] + 9));
return 0;
}
What is the output of the above program?- (a)14
- (b)24
- (c)30
- (d)20
Consider the following C program.
#include
int main () {
int a [4] [5] = {{1, 2, 3, 4, 5},{6, 7, 8, 9, 10},{11, 12,
13, 14, 15}, {16, 17, 18, 19, 20}};
printf ("%d\n", *(* (a+**a+2) +3));
return (0);
}
The output of the program is _______.What is the output of the following C code? Assume
that the address of x is 2000 (in decimal) and an integer
requires four bytes of memory.
#include <stdio.h>
int main()
{
unsigned int x[4][3] = {{1, 2, 3}, {4, 5, 6},
{7, 8, 9}, {10, 11, 12}};
printf("%u, %u, %u", x+3, *(x+3), *(x+2)+3);
}- (a)2036, 2036, 2036
- (b)2012, 4, 2204
- (c)2036, 10, 10
- (d)2012, 4, 6
For a C program accessing X[i][j][k], the following intermediate code is generated by a compiler. Assume that the size of an integer is 32 bits and the size of a character is 8 bits. t0 = i * 1024 t1 = j * 32 t2 = k * 4 t3 = t1 + t0 COMPILER DESIGN t4 = t3 + t2 t5 = x[t4] Which one of the following statements about the source code for the C program is CORRECT?
- (a)X is declared as "int X[32][32][8]".
- (b)X is declared as "int X[4][1024][32]".
- (c)X is declared as "char X[4][32][8]".
- (d)X is declared as "char X[32][16][2]".
Consider the following two C code segments. Y and X are one and two dimensional arrays of size n and n×n respectively, where 2 ≤n≤10. Assume that in both code segments, elements of Y are initialized to 0 and each element X[i][j] of array X is initialized to i+j. Further assume that when stored in main memory all elements of X are in same main memory page frame. Code segment 1: // initialize elements of Y to 0 // initialize elements of X[i][j] of X to i+j for (i=0; i<n; i++) Y[i] += X[0][i]; Code segment 2: // initialize elements of Y to 0 // initialize elements of X[i][j] of X to i+j for (i=0; i<n; i++) Y[i] += X[i][0]; Which of the following statements is/are correct? S1: Final contents of array Y will be same in both code segments S2: Elements of array X accessed inside the for loop shown in code segment 1 are contiguous in main memory S3: Elements of array X accessed inside the for loop shown in code segment 2 are contiguous in main memory
- (a)Only S2 is correct
- (b)Only S3 is correct
- (c)Only S1 and S2 are correct
- (d)Only S1 and S3 are correct
Self-check
Question 1
int a[10][20] is stored row-major, 4-byte int, base address 2000. What is the address of a[3][5]?
Show answerHide
.
Question 2
For double m[4][6] (8-byte double), what are sizeof m, sizeof m[0] and sizeof m[0][0]?
Show answerHide
bytes, a row is bytes, an element is 8 bytes.
Question 3
What does this print?
#include <stdio.h>
int main(void) {
int m[2][3] = {{1, 2, 3}, {4, 5, 6}};
printf("%d %d\n", *(*(m + 1) + 1), **m + *(*m + 2));
return 0;
}Show answerHide
*(*(m + 1) + 1) is . **m is , and *(*m + 2) is , so the sum is 4.
5 4Question 4
A lower triangular matrix is stored row by row in a 1-D array using 1-based indexing. At what 1-based position is element , and what is its address if the base is and each cell is bytes?
Show answerHide
Position . The address is .
Question 5
A[0..9][0..4] is stored column-major with 2-byte elements and base 100. What is the address of A[6][3]?
Show answerHide
There are 10 rows. .
Revision card
One-screen revision
- C 2-D arrays are arrays of rows, contiguous, row-major.
m + 1moves a row. - Row-major: . Column-major swaps the roles.
- 3-D: multiply each index by the product of the sizes of the dimensions to its right.
sizeof m / sizeof m[0]rows;sizeof m[0] / sizeof m[0][0]columns.- Parameters:
int a[][C]int (*a)[C]. Give all but the first dimension. - Lower triangular row-wise: . Tridiagonal row-wise: . (0-based.)
- Number of rows from bounds is .
References
- ·Kernighan and Ritchie, The C Programming Language, 2nd ed., Sections 5.7 Multi-dimensional Arrays, 5.9 Pointers vs. Multi-dimensional Arrays, 4.9 Initialization
- ·ISO/IEC 9899:2011 (C11), Sections 6.2.5 Types (array types), 6.5.2.1 Array subscripting, 6.7.6.2 Array declarators, 6.7.6.3 Function declarators (parameter adjustment), 6.7.9 Initialization
Verification
The animation replays a real run of an instrumented C program: every address and value is read from memory at each step, and the test suite checks that the 12 cells have consecutive 4-byte addresses and that m[2][1] is exactly (2 * 4 + 1) * 4 bytes after the start. All C programs on this page are compiled with -Wall -Wextra -Werror (the one marked warns is a deliberate trap) and run by npm run verify:c; they include enumeration programs that check the lower-triangular, tridiagonal and 3-D formulas. The general-bounds formulas (row-major and column-major) and the upper-triangular formula were additionally checked by a one-off enumeration script during the build, but that script is not part of the repository. The textbook section numbers were written from memory and have NOT been checked against the books. The prose and the GATE-pattern claims have NOT yet had a human textbook check (the independent second pass is described at the end of these notes). SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). No defect found. Status raised to self-checked; verified needs a textbook or human check.
last verified: never
My notes (saved in this browser)