Arrays Decay to Pointers: the Foundational C Subtlety
What This Concept Is
In C, the name of an array and a pointer to its first element are mostly the same thing, but not always. The rule:
In most expression contexts, an array expression is implicitly converted to a pointer to its first element. This is called array-to-pointer conversion, or informally array decay.
Places where decay does happen:
- using the array name in an expression (
a[3],a + 1,*a,printf("%p", (void*)a)) - passing the array to a function (
void f(int a[10])is identical tovoid f(int *a)) - returning an array from anything but the enclosing function (you cannot)
Places where decay does not happen:
- the operand of
sizeof(sizeof aon an array gives the bytes of the whole array) - the operand of
&(&ahas type "pointer to array of N T", not "pointer to T") - a string literal used to initialize a
chararray (it is copied, not decayed)
Why It Matters Here
Array decay is why the following are all equivalent:
a[i]means*(a + i)a[i]means*(i + a)meansi[a](legal, horrible)- a function parameter written
int a[]orint a[100]is reallyint *a
It is also why your intuition about sizeof inside a function is wrong. Inside void f(int a[10]), sizeof a is sizeof(int *), not 10 * sizeof(int).
Once you see this, most of C's "weird pointer code" becomes predictable.
Concrete Example
#include <stdio.h>
void print_size(int a[10]) { /* parameter decays to int * */
printf("inside: %zu\n", sizeof a); /* prints sizeof(int *), e.g., 8 */
}
int main(void) {
int arr[10] = {0};
printf("outside: %zu\n", sizeof arr); /* 40 on a 4-byte int */
int *p = arr; /* decay: p = &arr[0] */
printf("%d\n", arr[3]); /* 0 */
printf("%d\n", *(arr + 3)); /* 0, same thing */
printf("%d\n", 3[arr]); /* 0, equivalent, cursed but legal */
print_size(arr);
return 0;
}
arr and &arr[0] have different types (int[10] vs int *), but the first one decays to the second in p = arr.
Common Confusion / Misconception
"Arrays are passed by reference." No. Arrays decay to a pointer, and the pointer is passed by value. The function can modify the elements through the pointer, but cannot make the caller's name refer to a different array.
"int a[10] as a parameter means the function checks the length is 10." It does not. The 10 is documentation. The compiler reads it as int *a and keeps no size information.
"sizeof on an array in a function gives the array length." It gives sizeof(pointer). If you want the length inside the function, pass it as a separate parameter, or use a macro like #define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0])) only in the scope where a is still an array, not a parameter.
How To Use It
- Pass both the pointer and the length:
void f(int *a, size_t n). Never trust the[10]in a parameter. - Use
sizeof arr / sizeof arr[0]inside the same function the array is declared. - Read
int a[]asint *awhen it appears in a parameter list. - Remember that
&a(for an array) is a pointer to the whole array and has different pointer arithmetic. - Use
conston parameters you will not modify:void print_arr(const int *a, size_t n);.
Check Yourself
- What is the type of
ainint a[10];inside an expression likea + 1? - Why is
sizeof adifferent inside a function that tookint a[10]as a parameter? - If
char s[] = "hello";, what issizeof s? What isstrlen(s)? Why are they different?
Mini Drill or Application
Predict, then check:
int a[5]; printf("%zu %zu\n", sizeof a, sizeof &a);- Write
void fill(int *a, size_t n, int value); call it with a stack array and print results. - Write a function
int sum(const int *a, size_t n)and argue whyconstbelongs there. - Given
int m[3][4];, isman array of arrays or a pointer to a pointer? Justify. - Explain
char *p = "hi"; char a[] = "hi";- which ofp[0] = 'X';anda[0] = 'X';is safe, and why?