Why Arrays Decay into Pointers in C

In the C programming language, array decay refers to the implicit conversion of an array identifier into a pointer to its first element during expression evaluation. This article examines the historical and architectural reasons behind this design choice, tracing its roots from the B language into C, detailing the efficiency benefits regarding memory and function arguments, and outlining the primary contexts where decay occurs alongside the critical exceptions where it does not.

Historical Origins from B and BCPL

The primary reason arrays decay into pointers in C is backward compatibility with its predecessor languages, BCPL and B. In B, an array declaration actually created two distinct entities: a block of contiguous memory for the elements, and an explicit pointer variable that stored the address of that block. Whenever an array identifier was referenced, the runtime accessed this stored pointer.

When Dennis Ritchie developed C, he introduced typed data and structures. Ritchie wanted to eliminate the memory overhead of maintaining a separate, physical pointer variable for every array, especially on memory-constrained systems like the PDP-11. His solution was to make the array identifier represent the memory block itself rather than an independent pointer variable. However, to preserve existing B coding patterns—particularly the relationship between arrays and pointer arithmetic—the C compiler was designed to generate an address to the array’s first element whenever the array name appeared in an expression.

Performance and Function Call Semantics

C is strictly a pass-by-value language. If arrays did not decay into pointers, passing an array to a function would require copying every single element onto the call stack. For large datasets, this would cause severe performance degradation and rapid stack exhaustion.

Array decay allows functions to accept an array argument efficiently:

void process_data(int *data, size_t size);

When passing an array identifier to this function, the array decays into a pointer of type int * holding the address of data[0]. This mechanism achieves the performance benefits of pass-by-reference without introducing explicit reference types into the language grammar.

Unification with Pointer Arithmetic

Array decay is also fundamental to how C implements subscript notation. In C, the subscript operator is syntactically defined in terms of pointer arithmetic:

a[i] is identical to *(a + i)

For the expression a + i to be valid, a must have a pointer type. Array decay automatically converts a from type "array of T" to "pointer to T", allowing the compiler to perform pointer offset calculations based on the size of the underlying data type.

Contexts Where Arrays Do Not Decay

Array decay occurs in almost all expressions, but the C standard defines specific contexts where the array identifier preserves its true array type:

  1. The sizeof Operator: In sizeof(arr), decay is inhibited so the operator returns the total memory footprint of the array in bytes, rather than the size of a pointer.
  2. The Address-of Operator (&): In &arr, the expression evaluates to the address of the entire array (type T (*)[N]), rather than a pointer to a pointer.
  3. String Literal Initialization: When initializing a character array, such as char str[] = "text";, the string literal does not decay into a char *; instead, its contents are copied directly into the allocated array memory.
  4. The _Alignof Operator: In C11 and later, _Alignof(arr) evaluates the alignment requirement of the entire array type, not a pointer.