5.2.2 · D2C++ Programming

Visual walkthrough — References — lvalue references, difference from pointers

1,809 words8 min readBack to topic

This is the visual companion to the main note. Read it slowly; the pictures carry the argument.


Step 1 — A variable is a labelled box

WHAT. Before we can talk about a second name, we need to see what a first name is. When you write int x = 5;, the computer sets aside one small region of memory — think of it as a physical box — big enough to hold an int. It writes the number 5 inside, and it glues the label x to the outside of the box.

WHY start here. Every confusion about references comes from forgetting that a name and a box are two different things. The name is a sticker; the box is the storage. Keep them separate in your mind and nothing later will surprise you.

PICTURE. One box. Value 5 inside. Sticker x on the front. The box also lives at some address — a house number the machine uses to find it. Here we pretend that address is 0x100.

Figure — References — lvalue references, difference from pointers

Step 2 — Binding a reference glues a second sticker

WHAT. Now we write:

int& r = x;

Read the type int& as "int-reference" — the & here is not "address-of", it is part of the type, meaning "a name for an int". This line does not make a new box. It takes the sticker r and glues it onto the same box that already wears the sticker x.

WHY. This is the entire secret of references. = at the moment of declaration means bind, not copy. No memory is allocated for r; no 5 is duplicated. The box now simply has two stickers.

PICTURE. The identical box from Step 1, still holding 5, still at 0x100 — but now two stickers hang off it: x and r.

Figure — References — lvalue references, difference from pointers

Step 3 — Writing through either sticker hits the same box

WHAT. Execute r = 99;. Now r is already bound, so this = is ordinary assignment. But r names the shared box, so the 99 lands in that box.

WHY. Because there is only one box, "change r" and "change x" are the same physical action. This is what makes references useful and dangerous in equal measure.

PICTURE. The shared box: value crossed out from 5, now 99. Read it back through the x sticker — you get 99. The write went in one door and can be read from either.

Figure — References — lvalue references, difference from pointers

Step 4 — &r equals &x, because there is one address

WHAT. Apply the address-of operator & (now this & really does mean "give me the house number"). Ask for &r and for &x.

WHY. A reference has no house number of its own — it is not a box, it is a sticker. So "where is r?" can only mean "where is the box r names?" — which is exactly 0x100, the same answer as &x. Therefore &r == &x is true.

PICTURE. Two arrows, one labelled &x, one labelled &r, both pointing at the same box at 0x100. There is no separate 0x??? box for r — that space simply does not exist.

Figure — References — lvalue references, difference from pointers
Recall Why can't a reference have its own address?

Because it is not an object ::: it is another name for an existing object, so asking its address returns that object's address.

This is the deep reason sizeof(r) == sizeof(x) too — there's nothing extra to measure. See sizeof and object identity.


Step 5 — A pointer is a different box (the contrast)

WHAT. Now build a pointer, so you can feel the difference:

int* p = &x;

Read int* as "pointer-to-int". This does allocate a brand-new box — call it p at address 0x200 — and inside that box it stores a number: the address 0x100.

WHY. A pointer is a real object with a real value. Its value happens to be an address. That is why it can be reseated (write a different address into its box), be null (write 0), and be inspected (ask &p and get 0x200, its own address — different from &x).

PICTURE. Two boxes now. Box x at 0x100 holding 99. A separate box p at 0x200 holding the number 0x100, drawn as an arrow reaching over to box x. Contrast this with Step 2, where there was never a second box.

Figure — References — lvalue references, difference from pointers

See Pointers in C++ for the reseatable, nullable cousin in full.


Step 6 — Using them: auto-deref vs explicit *

WHAT. To read the value 99 through each:

  • Through the reference: just write r. The sticker leads straight to the box.
  • Through the pointer: write *p. The * means "follow the address inside p to the box it names", i.e. dereference.

WHY. The reference hides the hop because it is the target's name. The pointer needs * because you must explicitly say "don't give me the address I'm holding — give me what's at that address."

PICTURE. Left: r → box (one hop, invisible). Right: p → (arrow) → box (*p follows the arrow). Same destination, different journey.

Figure — References — lvalue references, difference from pointers

Step 7 — The payoff: swap without copies

WHAT. Put it together in the classic use — pass-by-reference (see Pass-by-value vs Pass-by-reference):

void swap(int& a, int& b) {
    int t = a;   // copy a's value into local t
    a = b;       // caller's a-box gets b's value
    b = t;       // caller's b-box gets old a
}
int x = 1, y = 2;
swap(x, y);        // x==2, y==1

WHY. Inside swap, a is a sticker glued onto the caller's x-box and b onto the y-box. No pointers, no *, no & at the call site — the parameters are the caller's variables under new names.

PICTURE. Caller's boxes x=1, y=2. Function's parameters a, b shown as extra stickers reaching back onto those very boxes. The three assignments animate the values crossing over.

Figure — References — lvalue references, difference from pointers

Step 8 — The degenerate cases you must never hit

WHAT. References forbid three things that a pointer allows. Each is a "picture that cannot exist."

WHY. A sticker must always be stuck to something real, right now, forever.

PICTURE. Three crossed-out panels:

  1. int& r; — a sticker floating with no box. Illegal: must bind at birth.
  2. int& r = x; r = otherbox; — you cannot peel the sticker off and move it; r = ... writes into x, it does not rebind. No reseating.
  3. A "null reference" — a sticker glued to nothing. Cannot exist.
Figure — References — lvalue references, difference from pointers

The one-picture summary

Everything on one canvas: the shared box with two stickers (reference), beside the two-box pointer setup, with the null/unbound/dangling panels marked forbidden.

Figure — References — lvalue references, difference from pointers
Recall Feynman retelling — say it like you'd tell a friend

A variable is a box with a value inside, an address on the outside, and a sticker (its name). Writing int& r = x; does not build a new box — it slaps a second sticker on the same box. So r and x are two names for one thing: change one, you change both; ask either one's address, you get the same house number, because there's only one house. A pointer is the opposite: it's a genuine second box whose stored value is an address, an arrow you draw to somewhere else — and because it's a real box, you can rub out its arrow (reseat), leave it blank (null), or ask where the box itself lives. To read through a pointer you must follow the arrow with *; a reference needs no such step because it is the name. That's why swap(int& a, int& b) just works: a and b are extra stickers reaching back onto the caller's boxes. And that's why three pictures are impossible: a sticker with no box (int& r;), a sticker you move to a new box (reseating), and a sticker glued to nothing (null). Sticker rules: Alias, Ready at birth, Never changes.


Connections