Introduction
A C array is a fixed number of objects of one type stored contiguously in memory. Arrays and pointers are closely tied: in most expressions an array name turns into a pointer to its first element, and the subscript a[i] is defined through pointer arithmetic. They are not the same thing, though, and this note sets out exactly the places where they differ: sizeof, &a versus a, and multidimensional arrays.
This note covers the C language side. Address formulas for arbitrary index ranges, column-major order and special matrices are in Arrays: Address Calculation and Special Matrices.
Conventions: C11, LP64 (4-byte int, 8-byte pointers). Byte offsets in examples are for gcc on x86-64 Linux; addresses such as 2000 are illustrative.
Prerequisites
- Pointers:
&,*, pointer arithmetic and its scaling.
Core concepts
Declaring and initialising arrays
int a[5];declares 5ints,a[0]toa[4]. The size must be a positive integer constant expression, except for variable length arrays (VLAs,int v[n];at block scope), which C11 makes an optional feature.int a[5] = {1, 2, 3};initialises the first three; the remaining elements are set to 0.int z[100] = {0};zeroes the whole array.int b[] = {5, 6, 7, 8};takes its size, 4, from the initialiser.- An automatic array with no initialiser has indeterminate elements; a static-duration array is zero-filled.
- Arrays cannot be assigned or compared as wholes:
a = b;does not compile, anda == bcompares addresses. - Indexing outside
0..n-1is undefined behaviour; C does no bounds checking.
The element count of an array (not a pointer) is sizeof a / sizeof a[0].
Array-to-pointer conversion (decay)
Rule (C11 6.3.2.1): an expression of type "array of " is converted to "pointer to " pointing at element 0, except when it is
- the operand of
sizeof, - the operand of unary
&, or - a string literal used to initialise an array, as in
char s[] = "hi";.
(The C11 text also lists _Alignof, but _Alignof accepts only a type name, so that item has no practical effect.)
Consequences:
a[i]is defined as*(a + i):adecays to&a[0], then pointer arithmetic moves elements.- Passing
ato a function passes&a[0]; the parameter is a pointer, andsizeofinside the function gives 8 (Functions and Parameter Passing). sizeof ais the size of the whole array: 20 forint a[5].
a versus &a
Both have the same address value, but different types.
| Expression | Type | Points to | + 1 advances by |
|---|---|---|---|
a (decayed) | int * | element 0 | 4 bytes (one int) |
&a[0] | int * | element 0 | 4 bytes |
&a | int (*)[5] | the whole array | 20 bytes (one array of 5 int) |
*&a | int [5] | (is the array) | sizeof *&a is 20 |
If a starts at 2000, then a + 1 is 2004 and &a + 1 is 2020.
Array of pointers versus pointer to array
Read the declaration from the name, giving [] priority over * unless parentheses intervene (Function Pointers and Reading Complex Declarations):
int *ap[3];is an array of 3 pointers toint:sizeof apis 24.int (*pa)[3];is a pointer to an array of 3int:sizeof pais 8.
Two-dimensional arrays
int m[3][4]; is an array of 3 elements, each of which is an array of 4 int. C stores it in row-major order: row 0, then row 1, then row 2.
| Expression | Type | Meaning |
|---|---|---|
m (decayed) | int (*)[4] | pointer to row 0 |
m + i | int (*)[4] | pointer to row ; advances bytes per row |
*(m + i), m[i] | int [4], decays to int * | row , as a pointer to its first element |
*(m + i) + j | int * | address of m[i][j] |
*(*(m + i) + j), m[i][j] | int | the element |
Element m[i][j] lies at byte offset from the start, where is the number of columns and the element size. With , and base 2000, m[2][1] is at .
![Row-major layout of int m[3][4] with pointer steps](/study-media/PCC/arrays-and-pointers-2d-array-layout.webp)
int m[3][4] as a grid and as the same 12 ints in row-major order in memory, with m + 1 stepping one row (16 bytes), *m + 1 stepping one int (4 bytes) and m[2][1] at byte offset 36.Passing a 2D array to a function. The parameter must give every dimension except the first: int sum(int r, int a[][4]) (equivalently int (*a)[4]). int a[][] does not compile, because the compiler needs the row length to compute a[i][j]. With a VLA parameter, int sum(int r, int c, int a[r][c]) also works.
Arrays of strings
const char *names[] = {"ab", "cde", "f"};is an array of 3 pointers to string literals:sizeof namesis 24, and the strings live elsewhere (read-only).char grid[3][4] = {"ab", "cde", "f"};is a character array holding copies:sizeof gridis 12, and each row is padded with'\0'. The rows are modifiable.
Worked examples
All outputs below are for C11 with gcc on x86-64 Linux (LP64).
Example 1 (sizes and partial initialisation).
int a[5] = {1, 2, 3};
printf("%zu %zu %zu\n", sizeof a, sizeof a[0], sizeof a / sizeof a[0]); /* 20 4 5 */
printf("%d %d %d\n", a[3], a[4], *(a + 2)); /* 0 0 3 */Example 2 (a + 1 versus &a + 1). For the same a, (char *)(a + 1) - (char *)a is 4 and (char *)(&a + 1) - (char *)a is 20. (void *)a == (void *)&a is 1: same address, different types.
Example 3 (2D access, every notation). With m[i][j] = 10 * i + j for int m[3][4]:
m[2][1],*(*(m + 2) + 1)are 21;*(m[1] + 3)is 13;(*(m + 1))[2]is 12.- Byte offsets:
m + 1is 16,*m + 1is 4,&m[2][1]is 36. sizeof m,sizeof m[0],sizeof m[0][0]are 48, 16, 4.int (*rp)[4] = m; rp++;makes(*rp)[0]equal tom[1][0], which is 10.- Inner-array bounds. With a declared
int m[3][4], formingint *flat = &m[0][0]does not permitflat[2*4+1]in strictly conforming C11: that access crosses the first inner array's boundary. Usem[2][1], a correctly typed pointer-to-array, or an allocation that is genuinely one flat int array. Do not claim universal support for the out-of-row idiom.
Example 4 (passing a 2D array). sum(2, m) with int m[2][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}} and int sum(int r, int a[][4]) returns 36.
Important properties and observations
a[i],*(a + i),*(i + a)andi[a]are the same expression.- An array decays to a pointer except under
sizeof, under&, and as a string literal initialising an array. aand&ahave the same address but step by an element versus by the whole array.- An array parameter is a pointer; only the first dimension of a multidimensional parameter may be omitted.
- Row-major layout: offset of
m[i][j]is . - Missing initialisers are zero; no initialiser at all leaves automatic arrays indeterminate.
- Arrays are not assignable; copy with a loop or
memcpy.
GATE examination perspective
- Pointer expressions on 2D arrays: translate each
*and+using the type table;m + 1and*m + 1differ by a factor of the row length. - Address questions: "an
intarraya[10][20]starts at 1000; find the address ofa[5][7]" is with 4-byteint. Check whether the question uses 0-based indices (C always does). sizeoftraps: inside a function the array parameter is a pointer.- Array of pointers versus pointer to array in declaration questions.
Summary
Sources
- ISO/IEC 9899:2011 (C11), committee draft N1570, (consulted 2026-11-09): Section 6.3.2.1 (array-to-pointer conversion, paragraph 3), 6.5.2.1 (array subscripting, including the row-major example in paragraph 4), 6.5.6 (pointer arithmetic), 6.7.6.2 (array declarators, VLAs), 6.7.9 (initialization).
- B. W. Kernighan and D. M. Ritchie, The C Programming Language, 2nd ed., Prentice Hall, 1988: Sections 5.3 (pointers and arrays), 5.7 (multi-dimensional arrays), 5.9 (pointers versus multi-dimensional arrays).