Arrays and Strings in C: Array-to-Pointer Decay, sizeof Traps and 2D Address Arithmetic

Learn what an array name means in C, why sizeof changes across a function boundary, and how to calculate 1D and 2D element addresses without guessing.

KnowledgeGate Team

Exam prep & CS education

Updated 30 Jul 20265 min read

An array name feels like a pointer, so it is tempting to treat the two as identical. That shortcut works until a sizeof, &a, string, or 2D-address question exposes the difference. The useful rule is precise: an array expression usually decays to a pointer, but the exceptions carry most of the traps.

What an array name really is

An array is a contiguous block of elements of one type. In int a[5], the object a contains five consecutive int values. The name a is a non-modifiable lvalue of array type, not a separate pointer variable stored in another box, so you cannot assign a new address to it.

In most expressions, a is converted, or decays, to a pointer to its first element. That is why a, &a[0], and a pointer initialised as int *p = a can hold the same numeric address.

Decay does not happen in three important contexts:

  1. When the array is the operand of sizeof.

  2. When the array is the operand of unary &.

  3. When a string literal initialises a character array.

These exceptions preserve the array as a whole object. They also explain why an array and a pointer can produce different types and sizes even when their printed addresses look equal.

Array-to-pointer decay measured with sizeof

Assume sizeof(int) = 4 bytes and a pointer occupies 8 bytes:

int a[5] = {10, 20, 30, 40, 50};

At the declaration site, sizeof(a) measures the complete array:

sizeof(a) = 5 elements x 4 bytes = 20 bytes
sizeof(a) / sizeof(a[0]) = 20 / 4 = 5 elements

The familiar length idiom is valid only while a is still an array in that scope. Now pass it to a function:

void f(int a[]) {
    printf("%zu\n", sizeof(a));
}

In a parameter list, int a[] is adjusted to int *a. Inside f, sizeof(a) therefore measures the pointer, not the original array. Under our assumptions it is 8, and the apparent length becomes 8 / 4 = 2. The function has not discovered that the array contains two elements. It has lost the length information and produced a meaningless result.

Pass the element count separately, or wrap the array in a structure when its size must travel with it.

Pointer arithmetic on a and &a

Let the five-element array begin at byte address 2000. The values of a and &a[0] are both 2000. Their type after decay is int *, so adding one advances by one int:

a + 1 = 2000 + 1 x 4 = 2004

The expression &a also has numeric address 2000, but its type is int (*)[5], a pointer to the whole five-element array. Adding one advances by 20 bytes:

&a + 1 = 2000 + 1 x (5 x 4) = 2020

Consequently, *(&a + 1) denotes an array beginning at 2020 and decays to that address when used as a pointer expression. Same starting number, different pointed-to type, different stride.

Indexing is pointer arithmetic in a convenient form. For any valid index i, a[i] is defined as *(a + i). That also makes the surprising i[a] equivalent, because addition is commutative: *(i + a) reaches the same element. It is legal, but a[i] is the readable form.

A 1D memory strip for int a[5] at base 2000, showing four-byte cells and where a, a+1, &a and &a+1 point.

The related pointers in C memory diagrams are useful when the types, rather than the address values, are causing confusion.

C strings are character arrays with a sentinel

Consider this declaration:

char s[] = "GATE";

The array contains five characters: 'G', 'A', 'T', 'E', and the terminating \0. Therefore sizeof(s) = 5, while strlen(s) = 4. sizeof counts storage in the array; strlen counts characters before the first null terminator.

There is another distinction worth keeping sharp:

char *p = "GATE";
char arr[] = "GATE";

p points at a string literal. Attempting p[0] = 'g' has undefined behaviour. arr is a private array initialised from the literal, so arr[0] = 'g' is valid. When allocating or copying a string, reserve one extra character for \0. Without it, functions such as strlen and printf with %s can read beyond the array.

2D arrays and row-major address arithmetic

C stores a true 2D array in row-major order. For int A[R][C] at base address B, the address of A[i][j] is:

B + (i x C + j) x sizeof(int)

Take int A[3][4] at base 1000, again with four-byte integers. For A[2][3]:

offset in elements = 2 x 4 + 3 = 11
offset in bytes    = 11 x 4 = 44
address            = 1000 + 44 = 1044

The row number is multiplied by the column count because two complete rows, eight elements, come before row 2. Three more elements lead to column 3.

A row-major grid for int A[3][4] at base 1000 with byte addresses, highlighting A[2][3] at address 1044.

When used as an expression, A decays to a pointer to its first row, with type int (*)[4], not int **. The compiler needs the column count to know that A + 1 moves by one complete row, or 4 x 4 = 16 bytes.

Array and string traps that recur

  • sizeof on an array parameter returns the pointer size, not the caller's array size.

  • A 2D-array parameter must retain its column dimension so pointer arithmetic has the correct row width.

  • Comparing two array expressions with == compares their decayed addresses, not their contents.

  • String functions expect a terminating \0; without it, they continue reading memory.

  • For int *p, p++ advances by sizeof(int), which is 4 bytes under our assumption, not one byte.

These are all consequences of type and representation. Once you identify the type before evaluating an expression, the apparent trick usually disappears.

The short version and your next step

GATE commonly turns these rules into output, size, and address-calculation questions. Check the official GATE 2026 portal from IIT Guwahati for the current examination information, since the organising institute changes by cycle.

Remember the core rule: an array name usually decays to a pointer, except with sizeof, unary &, and a string literal initialising an array. Then do arithmetic in units of the pointed-to type, not guessed byte jumps.

Build the ideas in the C Programming course, then practise the sizeof, string, and 2D-address families in the KnowledgeGate question bank. The wider Coding and Skill Development category gives you the next route after the language fundamentals.