Functions, Arrays & Pointers

Functions & Pass-by-Value vs. Pass-by-Reference

Why this matters

The last lesson covered & and * in isolation – one variable, one pointer, all inside main. This lesson adds exactly one new idea on top of that: every function parameter in C is a local copy. When you call f(x), the parameter inside f gets its own box in memory, initialized with a copy of x’s value – changing that box does nothing to the caller’s x. This is “pass-by-value,” and it’s the only mechanism C has for passing arguments; there is no pass-by-reference keyword. So how does scanf("%d", &n) manage to fill in your variable, if everything is passed by value? By having you pass the address of n, not n itself – scanf receives a copy of an address, and uses the exact *p = trick from the last lesson to reach back into your variable through it. That’s the whole answer, and it’s the entire subject of this lesson: doing on purpose, across a function call, what you already did by hand in main last lesson.

Tracing the worked example

try_swap_by_value(x, y) receives copies of x and y in its own parameters a and b. It swaps a and b perfectly – and then those copies vanish when the function returns. x and y in main were never touched.

swap_by_pointer(&x, &y) instead receives the addresses of x and y. Its parameters a and b are pointers (int *), copies of the addresses – but an address, unlike a value, still points at the original box. *a = *b doesn’t touch the pointer a; it reaches through a to overwrite the int that a points at, which is x itself. This is why swap_by_pointer can do what try_swap_by_value cannot.

The “out-parameter” pattern

A function can only return one value. When you need a function to hand back more than one result – or to modify something the caller already owns – the standard C idiom is an out-parameter: a pointer parameter the function writes through instead of returning. You’ll write several of these in this lesson’s exercises. The signature void divmod(int a, int b, int *quotient, int *remainder) is a direct description of what the function does: take two values in, write two results out through the pointers.

A pointer is just an address – and an uninitialized one is dangerous

int *p; declares a pointer but does not make it point anywhere valid – it holds garbage until you assign it (typically with &something, or later, the result of malloc). Dereferencing an uninitialized or NULL pointer is undefined behavior, not a friendly error message. Every pointer parameter in this lesson’s exercises is guaranteed to be a valid address by the time your function runs (the caller always passes &x for a real variable x) – but get in the habit of asking “is this pointer definitely valid?” before you dereference one, because the language will not ask it for you.

Try it
Output will appear here.

Exercises