Skip to main content

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 to void 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 a on an array gives the bytes of the whole array)
  • the operand of & (&a has type "pointer to array of N T", not "pointer to T")
  • a string literal used to initialize a char array (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) means i[a] (legal, horrible)
  • a function parameter written int a[] or int a[100] is really int *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

  1. Pass both the pointer and the length: void f(int *a, size_t n). Never trust the [10] in a parameter.
  2. Use sizeof arr / sizeof arr[0] inside the same function the array is declared.
  3. Read int a[] as int *a when it appears in a parameter list.
  4. Remember that &a (for an array) is a pointer to the whole array and has different pointer arithmetic.
  5. Use const on parameters you will not modify: void print_arr(const int *a, size_t n);.

Check Yourself

  1. What is the type of a in int a[10]; inside an expression like a + 1?
  2. Why is sizeof a different inside a function that took int a[10] as a parameter?
  3. If char s[] = "hello";, what is sizeof s? What is strlen(s)? Why are they different?

Mini Drill or Application

Predict, then check:

  1. int a[5]; printf("%zu %zu\n", sizeof a, sizeof &a);
  2. Write void fill(int *a, size_t n, int value); call it with a stack array and print results.
  3. Write a function int sum(const int *a, size_t n) and argue why const belongs there.
  4. Given int m[3][4];, is m an array of arrays or a pointer to a pointer? Justify.
  5. Explain char *p = "hi"; char a[] = "hi"; - which of p[0] = 'X'; and a[0] = 'X'; is safe, and why?

Read This Only If Stuck