Deep Dive Progress
0%
Deep Dive  ยท  Arrays & Pointers

The Row of Mailboxes Explanation

Arrays and pointers aren't two separate topics that happen to overlap โ€” an array IS a row of numbered mailboxes, and a pointer is just a note in your hand with one of those mailbox numbers written on it. Once that clicks, every "confusing" rule about arrays and pointers stops being a rule you memorize and starts being something you can derive yourself.

Memory as mailboxes
arr[i] = *(arr+i)
Array vs pointer
The sizeof trap
Arrays in functions
๐Ÿ“–

The Analogy: A Street of Mailboxes

Imagine a street with mailboxes in a perfect row, each with a house number painted on it โ€” 1000, 1004, 1008, 1012, 1016. That's exactly what an array is in your computer's memory: a row of storage slots, each with its own address, sitting right next to each other with no gaps.

When you write int arr[5] = {10, 20, 30, 40, 50};, C finds 5 mailboxes in a row and puts your numbers inside them.

Array in memory โ€” 5 mailboxes, addresses 4 bytes apart (int = 4 bytes)
1000
10
arr[0]
1004
20
arr[1]
1008
30
arr[2]
1012
40
arr[3]
1016
50
arr[4]

Now here's the part that unlocks everything else: the name arr itself is just the address of the first mailbox โ€” 1000. It isn't a box that contains 1000; it IS 1000, painted permanently on the street sign at the very start of the row. You can't repaint that sign. You can't move the row. That's the whole reason certain things you'd expect to work on arrays actually don't.

A pointer, on the other hand, is a completely different kind of thing: it's a small note you carry in your pocket, and on that note you write down whatever address you want โ€” 1000, 1008, or anywhere else in the city. You can scribble out the old number and write a new one any time.

๐Ÿ’ก The one-sentence version: An array name is a fixed address baked into the layout of memory. A pointer is a movable variable that happens to be storing an address. Same job (pointing at data), completely different nature (permanent sign vs. a note you can rewrite).
example 1
1

Proof: arr Is Already an Address

The simplest possible example

Don't take the analogy on faith โ€” let's ask C directly. If arr is really the address of the first mailbox, then printing arr and printing &arr[0] (the "address of the box at index 0") should show the exact same number.

Example 1 ยท arr_is_an_address.c
arr_is_an_address.c
C
#include <stdio.h>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};

    printf("arr        = %p\n", (void*)arr);
    printf("&arr[0]    = %p\n", (void*)&arr[0]);

    return 0;
}
terminal
output
arr        = 0x7ffee2a1c9c0
&arr[0]    = 0x7ffee2a1c9c0

Identical. Not similar โ€” identical. That's not a coincidence built into this one example; it's true for every array, every time. arr was never "a box holding an address" the way a pointer is โ€” writing arr in your code always means "the address where this array starts," full stop.

example 2
2

A Pointer Walking Down the Row

The note that gets rewritten

Now let's actually use a pointer as the "note in your pocket." We copy arr's address onto the note (ptr = arr;), then rewrite the note to point one mailbox further each time with ptr++.

ptr starts at arr (1000), then ptr++ moves it forward one whole int (4 bytes) at a time
ptr โ†’
1000
10
arr[0]
1004
20
arr[1]
1008
30
arr[2]
1012
40
arr[3]
1016
50
arr[4]
Example 2 ยท pointer_walk.c
pointer_walk.c
C
#include <stdio.h>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *ptr = arr;   // copy the address onto the "note"

    for (int i = 0; i < 5; i++) {
        printf("ptr = %p, *ptr = %d\n", (void*)ptr, *ptr);
        ptr++;   // rewrite the note to the next mailbox
    }

    return 0;
}
terminal
output
ptr = 0x7ffee2a1c9c0, *ptr = 10
ptr = 0x7ffee2a1c9c4, *ptr = 20
ptr = 0x7ffee2a1c9c8, *ptr = 30
ptr = 0x7ffee2a1c9cc, *ptr = 40
ptr = 0x7ffee2a1c9d0, *ptr = 50
๐Ÿ’ก Notice the address jumps by 4 each time (c9c0 โ†’ c9c4 โ†’ c9c8...), not by 1. ptr++ doesn't mean "add 1 to the number" โ€” it means "move to the next mailbox," and since each mailbox here holds an int (4 bytes), the address jumps by 4. A char* would jump by 1, a double* by 8. The pointer's type tells C how big one step is.
example 3
3

arr[i] Is Secretly *(arr + i)

The rule behind the notation

Here's the reveal: square-bracket indexing isn't its own separate feature of C. arr[i] is just shorthand that the compiler expands into *(arr + i) โ€” "walk i mailboxes down from the start, then open that box." That's it. That's the entire trick behind array indexing.

Example 3 ยท indexing_is_pointer_math.c
indexing_is_pointer_math.c
C
#include <stdio.h>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};

    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d   |   *(arr+%d) = %d\n",
               i, arr[i], i, *(arr + i));
    }

    // Fun (but confusing) proof: C doesn't care which side the number is on
    printf("\narr[2]   = %d\n", arr[2]);
    printf("2[arr]   = %d\n", 2[arr]);   // yes, this really compiles

    return 0;
}
terminal
output
arr[0] = 10   |   *(arr+0) = 10
arr[1] = 20   |   *(arr+1) = 20
arr[2] = 30   |   *(arr+2) = 30
arr[3] = 40   |   *(arr+3) = 40
arr[4] = 50   |   *(arr+4) = 50

arr[2]   = 30
2[arr]   = 30

2[arr] looks like a typo, but it compiles and works, because arr[2] was never anything more than *(arr + 2) โ€” and addition doesn't care about order, so *(arr + 2) and *(2 + arr) are the same thing. Nobody writes code this way, but seeing it work proves the rule isn't a metaphor โ€” it's literally how the compiler treats [ ].

example 4
4

Why arr++ Doesn't Compile (But ptr++ Does)

The one real difference

This is where the analogy earns its keep. ptr is a note in your pocket โ€” of course you can cross out the number and write a new one, that's what pockets and notes are for. But arr is a street sign painted into the layout of memory itself. There's no "pocket" to put a new number into โ€” asking to change arr is like asking to repaint the fixed starting address of the row. C simply won't allow it.

Example 4 ยท array_vs_pointer.c
array_vs_pointer.c
C
#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};
    int *ptr = arr;

    ptr++;                 // fine โ€” the note can be rewritten
    printf("*ptr after ptr++ = %d\n", *ptr);

    // arr++;              // COMPILE ERROR if uncommented:
    // "lvalue required as increment operand"
    // arr is not a variable holding an address โ€” it IS the address.
    // There's no storage location for "arr" to update.

    printf("arr[0] is still     = %d\n", arr[0]);

    return 0;
}
terminal
output
*ptr after ptr++ = 2
arr[0] is still     = 1
Array name (arr)Pointer variable (ptr)
What it isA fixed address, baked into memory layoutA variable that stores an address
Can you reassign it?No โ€” arr++ is a compile errorYes โ€” ptr++, ptr = &x, anything
Takes up its own memory?No extra memory โ€” it's not a "box," it's a labelYes โ€” the pointer itself lives somewhere and needs storage
sizeof itSize of the whole array (see next section)Size of one address (8 bytes on a 64-bit system)
example 5
5

The sizeof Trap โ€” Where the Analogy Saves You

Arrays remember; pointers forget

An array name isn't just an address โ€” the compiler also remembers how many mailboxes are in the row, because it laid them out itself. A pointer only ever knows one address; it has no idea how many boxes come after it. This difference explains a bug almost every C beginner hits at least once.

Example 5 ยท sizeof_trap.c
sizeof_trap.c
C
#include <stdio.h>

void printSize(int arr[]) {   // secretly becomes int* arr โ€” it decays!
    printf("Inside function : sizeof(arr) = %zu bytes\n", sizeof(arr));
}

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *ptr = arr;

    printf("In main, sizeof(arr) = %zu bytes  (5 ints x 4 bytes)\n", sizeof(arr));
    printf("In main, sizeof(ptr) = %zu bytes  (just one address)\n", sizeof(ptr));

    printSize(arr);   // arr "decays" into a plain int* the moment it's passed

    return 0;
}
terminal โ€” 64-bit system
output
In main, sizeof(arr) = 20 bytes  (5 ints x 4 bytes)
In main, sizeof(ptr) = 8 bytes  (just one address)
Inside function : sizeof(arr) = 8 bytes
โš ๏ธ The classic bug. The moment an array is passed into a function, it "decays" into a plain pointer โ€” the function only ever receives the starting address, never the mailbox count. sizeof(arr) inside printSize() reports 8 (one address), not 20. This is exactly why array-processing functions in C always take a separate length parameter โ€” void printSize(int arr[], int length) โ€” the function has no other way to know how many boxes to walk through.
๐Ÿ’ก Putting it all together: Every pointer rule you've seen in earlier lessons โ€” pointer arithmetic, passing arrays to functions, why char* and int* step differently โ€” is just this one mailbox picture applied in different situations. There was never a separate "array world" and "pointer world"; arrays are what memory actually looks like, and pointers are the tool C gives you to talk about addresses in that memory.
quiz
Q

Quick Quiz

Question 1 of 5

What does the array name arr actually represent?

Question 2 of 5

arr[i] is shorthand for which expression?

Question 3 of 5

Why does arr++ fail to compile, while ptr++ works fine?

Question 4 of 5

If int arr[10], what does sizeof(arr) return inside the same function it was declared in?

Question 5 of 5

Why must array-processing functions take a separate length parameter?

โœ“

Lesson Checklist

  • I can explain memory as a "row of mailboxes" in my own words
  • I understand arr and &arr[0] are literally the same address
  • I can explain why ptr++ moves by the size of the data type, not by 1
  • I understand arr[i] is really *(arr + i)
  • I can explain why arr++ doesn't compile but ptr++ does
  • I know sizeof(arr) gives total bytes, but decays to pointer size in a function
  • I understand why array functions need a length parameter
  • I completed the quiz