🎯 Pointers & Memory
Arrays vs pointers: the great confusion
▶ Open the interactive lesson — free, no signupBy 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:
#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;
}$ 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:
#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;
}$ 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 commutes — 3[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:
| expression | what you get |
|---|---|
sizeof a | size of the whole array in bytes (e.g. 16 for int a[4]) |
&a | pointer 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:
#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;
}$ 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 bytesAn 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)