The C Path — learn C, visually

🎯 Pointers & Memory

Arrays vs pointers: the great confusion

⏱ 12 min · free interactive lesson · quizzes, visualizations & a real compiler

▶ Open the interactive lesson — free, no signup
Why you're learning this

By now arrays and pointers keep acting suspiciously alike — *p here, a[i] there, loops that work with either. One hidden rule explains the whole illusion, and it also answers two questions that bite every C learner: why sizeof seems to lie about an array inside a function, and why every function that takes an array forces you to pass its length separately.

"Arrays are just pointers" is the most repeated wrong sentence in C education. Arrays and pointers are different things — an array is its elements; a pointer refers to something else. The confusion exists because of one sneaky rule, and once you know it, everything snaps into focus.

The rule: arrays decay

In almost every expression, an array name is automatically converted — "decays" — to a pointer to its first element. Write a, get &a[0]. That's it. That's the whole trick behind a decade of confusion:

decay.c
#include <stdio.h>

int main(void) {
    int a[4] = {10, 20, 30, 40};

    printf("a        = %p\n", (void *)a);       /* decays!      */
    printf("&a[0]    = %p\n", (void *)&a[0]);   /* same address */
    printf("*a       = %d\n", *a);              /* a[0]         */
    printf("*(a + 2) = %d\n", *(a + 2));        /* a[2]         */
    printf("sizeof a = %zu\n", sizeof a);       /* NO decay: 16 */
    return 0;
}
terminal
$ gcc decay.c -o decay && ./decay
a        = 0x7ffe0b8c1540
&a[0]    = 0x7ffe0b8c1540
*a       = 10
*(a + 2) = 30
sizeof a = 16
# a acts like &a[0] everywhere — EXCEPT inside sizeof

Notice the last line: sizeof a said 16, not 8. That's our first clue that a is not actually a pointer — more on that below.

🧠 Checkpoint: In most expressions, an array name evaluates to…

  • The whole array, copied
  • A pointer to its first element
  • The number of elements
  • Its first element’s value
Show answer

A pointer to its first element — That’s decay: a becomes &a[0]. The array itself never moves; only its starting address is handed around.

a[i] is defined as *(a + i)

Indexing isn't an array feature — it's a pointer feature! The standard literally defines a[i] to mean *(a + i): decay the array, do pointer arithmetic, dereference. And since addition commutes, *(a + i) == *(i + a)… which means this monstrosity compiles:

commute.c
#include <stdio.h>

int main(void) {
    int a[4] = {10, 20, 30, 40};

    printf("a[2]     = %d\n", a[2]);
    printf("*(a + 2) = %d\n", *(a + 2));   /* the definition   */
    printf("*(2 + a) = %d\n", *(2 + a));   /* + commutes...    */
    printf("2[a]     = %d\n", 2[a]);       /* ...so this works */
    return 0;
}
terminal
$ gcc commute.c -o commute && ./commute
a[2]     = 30
*(a + 2) = 30
*(2 + a) = 30
2[a]     = 30
🎉

Yes, 2[a] is legal C. It desugars to *(2 + a), same as a[2]. Wonderful for winning bar bets, terrible for code review. Use this power only for good.

🧠 Checkpoint: Why does 3[a] compile and equal a[3]?

  • It’s a GCC extension
  • Because a[i] is defined as *(a+i), and addition commutes
  • It doesn’t compile
  • It only works for char arrays
Show answer

Because a[i] is defined as *(a+i), and addition commutes3[a]*(3 + a)*(a + 3)a[3]. Indexing is pointer arithmetic wearing square brackets.

When decay does NOT happen

There are exactly three main escapes from decay, and they're where the array's true nature shows:

expressionwhat you get
sizeof asize of the whole array in bytes (e.g. 16 for int a[4])
&apointer to the whole array — type int (*)[4], same address, different type: &a + 1 jumps 16 bytes!
char s[] = "hi"a string literal initializing an array copies the characters — no decay

🤔 Given int a[8]; on a 64-bit machine — what are sizeof a, sizeof &a[0], and sizeof (a + 0)?

Think first

sizeof a = 32 (8 ints × 4 bytes — no decay inside sizeof). sizeof &a[0] = 8 (it’s an int *, and pointers are 8 bytes here). sizeof (a + 0) = 8 too — the arithmetic forced a to decay into a pointer first! The moment an array participates in an expression, it’s a pointer.

Array parameters are a polite fiction

Now the kicker. When you declare a function parameter as an array, C silently rewrites it as a pointer — void f(int a[10]), void f(int a[]), and void f(int *a) declare the exact same function. The 10 is decorative. Consequences:

param.c
#include <stdio.h>

void inspect(int a[100]) {          /* the 100 is a lie:     */
    printf("inside : %zu\n", sizeof a);  /* a is an int*    */
    a[0] = 999;                     /* modifies CALLER's array */
}

int main(void) {
    int a[100] = {1};
    printf("outside: %zu\n", sizeof a);
    inspect(a);
    printf("a[0] is now %d\n", a[0]);
    return 0;
}
terminal
$ gcc param.c -o param && ./param
param.c:5:35: warning: 'sizeof' on array parameter 'a' will
    return size of 'int *' [-Wsizeof-array-argument]
outside: 400
inside : 8
a[0] is now 999
# inside the function, "a" is just a pointer — 8 bytes
⚠️

An array never travels through a function call. Only the address of its first element does — which is also why arrays "pass by reference" (the callee can modify your elements) and why every array-taking function needs a separate length parameter: void f(int *a, size_t n). The length doesn't ride along; you must carry it yourself.

🧠 Checkpoint: Inside void f(int a[10]), what is sizeof a?

  • 40
  • 10
  • sizeof(int *) — the parameter is really a pointer
  • A compile error
Show answer

sizeof(int *) — the parameter is really a pointer — Array parameters are rewritten to pointers before the function body ever sees them. The declared size is documentation at best — which is why functions take an explicit length argument.

This spot has an interactive editor widget — open the interactive lesson to play with it.

Armed with decay, you're ready for C's most famous "array of char with a twist" — strings.

▶ Practice this lesson interactively (with live gcc)