Expanding on Doc Brown's answer a bit...
In B, when you declared an array as
auto a[10];
the compiler would set aside an extra word to store an offset to the first element of the array (essentially a pointer):
+–––+ +–––+
a: | | –––> | | a[0]
+–––+ +––-+
| | a[1]
+–––+
...
+–––+
| | a[9]
+–––+
The array subscript operation a[i]
was defined as *(a + i)
; given the starting address stored in a
, offset i
words and dereference the result.
When he was designing C, Ritchie wanted to keep B's a[i] == *(a + i)
array behavior, but he didn't want to set aside storage for that explicit pointer. When you declare an array in C as:
T a[10]; // for any type T
what you get in memory is:
+–––+
a: | | a[0]
+–––+
| | a[1]
+–––+
...
+–––+
| | a[9]
+–––+
No storage is set aside for a pointer to the first element. Instead, he came up with the rule that unless it is the operand of the sizeof
, _Alignof
, or the unary &
operators, an expression of type ”N-element array of T
" will converted, or "decay" to an expression of type "pointer to T
" and its value will be the address of the first element of the array.
This way, a[i] == *(a + i)
still works, without needing to actually store a pointer value anywhere. a
is not a pointer, but under most circumstances evaluates to a pointer value (although you can use the subscript operator on a pointer variable as well).
The downside of this rule is that array expressions lose their array-ness under most circumstances, including when passed as function arguments.
This behavior is unique to arrays; structs use a different mechanism to compute member access.