5.1.7 · D2C Programming

Visual walkthrough — Functions — declaration vs definition, prototypes, call by value

1,951 words9 min readBack to topic

Step 1 — A variable is a labelled box in memory

WHAT. Before we can talk about copying, we need to know what gets copied. In C, when you write int a = 1;, the computer sets aside a small region of memory — think of it as a box — gives it a name (a) and stores a value (1) inside.

WHY. Everything about call by value is about which box a change lands in. So we must be able to point at a specific box and say "this one, not that one." A box has three things worth naming:

  • its name a — how you refer to it in code,
  • its value 1 — what is stored inside right now,
  • its addresswhere in memory the box physically sits (we draw it as a house number).

PICTURE. Two boxes a and b, each with a name written on the lid, a value inside, and a house-number address underneath.

Figure — Functions — declaration vs definition, prototypes, call by value

Step 2 — Calling a function builds a fresh scratchpad

WHAT. When main calls square(v), the machine does not let square reach into main's boxes. Instead it carves out a brand-new region of memory — a stack frame — just for this call. Inside that frame it creates the parameter box n.

WHY. A function must be reusable and predictable. If every call scribbled directly on the caller's boxes, two functions could quietly stomp on each other. Giving each call its own private scratchpad keeps them isolated. (The mechanism of how these frames stack up is the subject of Stack and Function Call Mechanism.)

PICTURE. main's frame on the left holds box v = 5. A new frame for square appears on the right, empty, waiting for its parameter n.

Figure — Functions — declaration vs definition, prototypes, call by value

Step 3 — The argument's VALUE is copied across

WHAT. Now the copy happens. C reads the value inside v (which is 5) and writes that number into n. This is the single act named "call by value."

WHY. The parameter n is initialised from the value, not linked to the box v. It is as if you photocopied the number 5 and pinned the photocopy into n. After this instant, v and n have no connection — cut the wire, they just happen to both hold 5 for now.

PICTURE. A pink arrow carries the number 5 out of v and drops it into n. The arrow is a copy, not a bridge — once drawn, it is gone.

Figure — Functions — declaration vs definition, prototypes, call by value

Step 4 — Inside the function, changes stay local

WHAT. Suppose square did more than compute: say it ran n = n * n;. That multiplies the number in n (making it 25) and writes it back into n — square's own box.

WHY. The assignment target is n, and n lives only in square's frame. There is no line of sight from n back to v. So v is physically untouched; it still reads 5.

PICTURE. n updates from 5 to 25 inside square's frame. Box v in main's frame is drawn faded/greyed to shout: nothing changed here.

Figure — Functions — declaration vs definition, prototypes, call by value

Step 5 — The frame is destroyed on return

WHAT. square finishes by handing back a value (25), which gets copied into r in main's frame. Then square's entire frame — including box n — is thrown away.

WHY. This seals the isolation. Even if the function had changed n, that box no longer exists after the call, so there is provably no way for the change to leak upward. The only thing that survives the return trip is the single value that was explicitly returned.

PICTURE. Square's frame is crossed out / dissolving; a yellow arrow carries the return value 25 into r. Box n vanishes with the frame.

Figure — Functions — declaration vs definition, prototypes, call by value
Recall Why can nothing leak out?

Because the change lands in a box (n) that is destroyed the instant the function returns ::: only the explicitly returned value survives, copied into the caller's box.


Step 6 — Edge case: the swap that fails

WHAT. People expect swap(a, b) to exchange a and b. Let us run it under the rules we just built and watch it fail.

WHY. This is the classic trap. swap receives copies x and y. It faithfully swaps those two copies inside its own frame — but a and b in main are different boxes at different addresses, so they never move.

PICTURE. main holds a=1, b=2. Copies flow into x=1, y=2. Inside swap's frame the arrows cross (x and y swap to 2 and 1), while a and b sit unmoved. Then swap's frame dies — taking the swapped copies with it.

Figure — Functions — declaration vs definition, prototypes, call by value

Step 7 — The fix: copy the ADDRESS, not the value

WHAT. To let swap reach the originals, we hand it the addresses of a and b using the & operator. The parameters become pointers (int *x), and *x means "the box that this address points to."

WHY. Call by value still applies — but now the value being copied is an address. A photocopy of a house number still leads to the same house. So *x = ... writes through the copied address, straight into main's real box. (Pointers get their own full treatment in Pointers in C.)

PICTURE. x holds the address of a (drawn as an arrow pointing back at box a), y points back at b. Writing *x and *y reaches across the frames and swaps the real boxes.

Figure — Functions — declaration vs definition, prototypes, call by value

The one-picture summary

Figure — Functions — declaration vs definition, prototypes, call by value

The whole derivation on one board: two frames side by side. On the value path (top) a number is photocopied into the parameter, played with, and the result thrown away with the frame — original untouched. On the address path (bottom) an address is photocopied, and following it edits the original box for real. Same rule (copy the value), two outcomes, because what you copy differs.

Recall Feynman retelling of the whole walkthrough

Imagine your variables are labelled shoeboxes on a shelf. When you call a function, the computer clears a new shelf for it and puts down empty boxes for its parameters. Then it photocopies the numbers from your boxes and pops the photocopies into the function's boxes. The function scribbles all over its photocopies — squares them, swaps them, whatever — but your original boxes upstairs never move, because the function literally cannot see them. When the function is done, its whole shelf is swept into the bin; the only thing that survives is the one number it hands back to you. That is why an ordinary swap fails: it swaps photocopies, then bins them. The one trick to reach your real boxes is to photocopy the house number of a box instead of its contents — a copied address still points at the same house, so writing "through" it changes your real data. That is pointers, and it is still call by value, because you copied the address itself.


Connections

  • Pointers in C — the address path (Steps 7–8) in full.
  • Stack and Function Call Mechanism — how the frames in Steps 2 and 5 are built and destroyed.
  • Scope and Lifetime of Variables — why the parameter box dies on return.
  • Arrays as Function Arguments — a place where the "copy" rule surprises you (arrays decay to addresses).
  • Recursion — many frames of the same function, each with its own private copies.
  • Header Files and Separate Compilation — where the prototype lives when the definition is elsewhere.