Exercises — Functions — declaration vs definition, prototypes, call by value
L1 — Recognition
These test whether you can name and spot the ideas: declaration vs definition, what a prototype contains, what "copy" means.
Exercise 1.1 (L1)
Look at each line. Label it D = pure declaration (prototype), F = definition, or C = call.
int max(int a, int b); // (i)
int y = max(3, 9); // (ii)
int max(int a, int b) { return a>b ? a : b; } // (iii)
double area(double); // (iv)Recall Solution
- (i) D — ends with
;, has a signature, no body{...}. Pure prototype. - (ii) C — it uses
max, passing3and9. This is a call. - (iii) F — signature plus
{ ... }body. A definition (and automatically also a declaration). - (iv) D — declaration with the parameter name omitted. Names are optional in a prototype;
double area(double);is perfectly legal.
The one-glance rule: does the line end in ; with no {} → declaration. Has {} → definition.
Appears inside other code being evaluated → call.
Exercise 1.2 (L1)
A function may be declared ___ times and defined ___ times. Fill the blanks.
Recall Solution
Declared as many times as you like (each is just a repeated promise); defined exactly one time. This is the One Definition Rule. Repeating the body would give the linker two machines with the same name — it cannot choose, so it errors.
L2 — Application
Now you run the copy rule in your head and predict output.
Exercise 2.1 (L2)
What does this print?
int bump(int x) { x = x + 5; return x; }
int main(void) {
int k = 10;
int r = bump(k);
printf("%d %d\n", r, k);
return 0;
}Recall Solution
Answer: 15 10.
bump(k)copies the value10into the parameterx. Think ofxas a fresh sticky note that starts life reading10.- Inside,
x = x + 5changes that sticky note to15, and it is returned →r = 15. kback inmainis a different piece of memory; nothing touched it, sokis still10.
This is call by value: the function edits a copy, the original is safe.
Exercise 2.2 (L2)
Predict the two printed lines.
void tryChange(int n) { n = 999; }
int main(void) {
int a = 7;
tryChange(a);
printf("%d\n", a);
printf("%d\n", tryChange ? 1 : 0); // (the address of a function is nonzero)
return 0;
}Recall Solution
Answer: 7 then 1.
tryChange(a)copies7inton. Settingn = 999scribbles on the copy only. When the call ends, that copy (and its whole stack frame) is destroyed. Soaprints 7.tryChangeused without()is the function's address, which is never zero, sotryChange ? 1 : 0is 1. (You do not need this trick often — it just confirms the function exists as an object in memory.)
L3 — Analysis
Here you diagnose why code compiles, warns, or misbehaves.
Exercise 3.1 (L3)
The compiler rejects this. Explain precisely why, and give the smallest fix.
#include <stdio.h>
int main(void) {
printf("%d\n", cube(3));
return 0;
}
int cube(int n) { return n*n*n; }Recall Solution
Why it breaks: C compiles top to bottom in one pass. When it reaches cube(3) inside
main, it has not yet seen any mention of cube — the definition comes later in the file.
The compiler has no prototype, so it cannot know cube takes an int and returns an int. Modern
C (C99+) treats this implicit declaration as an error.
Smallest fix: add a prototype above main:
#include <stdio.h>
int cube(int n); // <-- the promise, now the compiler knows the shape
int main(void) { printf("%d\n", cube(3)); return 0; }
int cube(int n) { return n*n*n; }(Alternative fix: move the whole definition above main.) With the prototype, cube(3) evaluates
to 27.
Exercise 3.2 (L3)
This compiles across two files but prints garbage. Find the bug.
/* file: math.c */
long dbl(long x) { return 2*x; }
/* file: main.c */
int dbl(int x); // programmer's own prototype
int main(void){ printf("%d\n", dbl(1000000)); }Recall Solution
The bug: prototype and definition disagree. main.c promises int dbl(int), but the real
function in math.c is long dbl(long). Because they are in separate files, the compiler of
main.c never sees the true definition — it trusts your wrong prototype. At the machine level
main passes an int where the real function reads a long, and interprets a long return as an
int. The sizes/registers mismatch → silent garbage (undefined behaviour).
Fix: the declaration must be identical to the definition. Put the correct prototype in a shared header:
/* math.h */ long dbl(long x);and #include "math.h" in both files, so there is exactly one source of truth. See
Header Files and Separate Compilation.
L4 — Synthesis
Now you build the escape hatch from call by value: pointers. A pointer is a variable that
stores an address — the house number of another variable. &a means "address of a"; *p
means "the value living at the address p". See Pointers in C and
Stack and Function Call Mechanism.
The figures below show what memory actually does for a plain swap vs a pointer swap.


Exercise 4.1 (L4)
Write a function addTax that takes the address of a double price and a double rate (e.g. 0.1
for 10%), and multiplies the caller's price by (1 + rate) in place (no return value).
Then predict the output of using it on price = 200.0, rate = 0.1.
Recall Solution
void addTax(double *price, double rate) {
*price = *price * (1.0 + rate); // *price reaches back into the caller's variable
}
int main(void) {
double p = 200.0;
addTax(&p, 0.1); // pass the ADDRESS of p, and the value 0.1
printf("%.2f\n", p); // 220.00
return 0;
}Walkthrough:
&pisp's house number. We copy that number into the parameterprice. (Still call by value — we copied an address value! — but the address points home.)*pricemeans "the double stored at that house" — i.e. the realpinmain.*price = *price * 1.1computes200 * 1.1 = 220and writes it into the caller'sp.
Answer: prints 220.00.
Exercise 4.2 (L4)
Fill in swap so that after swap(&a, &b) the caller's a and b are truly exchanged. Trace the
memory for a = 4, b = 9.
void swap(int *x, int *y) {
int t = ____;
*x = ____;
*y = ____;
}Recall Solution
void swap(int *x, int *y) {
int t = *x; // t = value at a's house = 4
*x = *y; // a's house now gets value at b's house = 9
*y = t; // b's house gets old value of a = 4
}Trace (a=4, b=9), see figure s02:
| step | *x (=a) |
*y (=b) |
t |
|---|---|---|---|
| start | 4 | 9 | — |
t = *x |
4 | 9 | 4 |
*x = *y |
9 | 9 | 4 |
*y = t |
9 | 4 | 4 |
Result: a = 9, b = 4 — really swapped. The pointers reached into main's memory. Contrast
the plain-value version in figure s01, where only the copies inside the frame swap.
L5 — Mastery
Combine everything: multi-file design, copy semantics, and a subtle pointer case.
Exercise 5.1 (L5)
Design a two-file program: a header stats.h, an implementation stats.c, and main.c. Provide a
function minmax that takes an array of int, its length, and returns both the minimum and the
maximum to the caller. Since a C function returns only one value, use pointer parameters for the
two outputs. State every prototype and body, then predict the output for {5, 2, 9, 1, 7}.
Background you may assume: an array passed to a function decays to a pointer to its first element — see Arrays as Function Arguments.
Recall Solution
/* stats.h -- declarations only (the promise) */
#ifndef STATS_H
#define STATS_H
void minmax(const int *a, int n, int *outMin, int *outMax);
#endif/* stats.c -- the one definition */
#include "stats.h"
void minmax(const int *a, int n, int *outMin, int *outMax) {
int mn = a[0], mx = a[0];
for (int i = 1; i < n; i++) {
if (a[i] < mn) mn = a[i];
if (a[i] > mx) mx = a[i];
}
*outMin = mn; // write results back through the caller's addresses
*outMax = mx;
}/* main.c */
#include <stdio.h>
#include "stats.h"
int main(void) {
int v[] = {5, 2, 9, 1, 7};
int lo, hi;
minmax(v, 5, &lo, &hi); // &lo, &hi = addresses to fill in
printf("min=%d max=%d\n", lo, hi);
return 0;
}Why this design:
- The header carries the single shared prototype so both files agree on the signature — no L3-style mismatch.
minmaxneeds to hand back two numbers; a function returns one value, so the extra outputs go through pointer parametersoutMin,outMax.*outMin = mnwrites straight intomain'slo.- The array
vdecays to a pointer, so the function reads the real elements (arrays are never copied element-by-element).
Trace over {5,2,9,1,7}: min sinks 5→2→1; max climbs 5→9.
Answer: min=1 max=9.
Exercise 5.2 (L5)
Spot the bug and its consequence:
int* makeCounter(void) {
int c = 0;
return &c; // return the address of a local
}Recall Solution
Bug: returning the address of a local variable. c lives in makeCounter's stack frame
— a scratchpad that is destroyed the instant the function returns. The returned address then
points at freed, reusable memory (a dangling pointer). Reading *p later is undefined
behaviour: it may show 0, garbage, or crash.
Why it's tempting: it mirrors the legal pattern of returning a computed value
(return c; is fine — that copies the number out). But a local's address has no life beyond the
call. See Scope and Lifetime of Variables.
Fixes:
- Return the value:
int makeCounter(void){ int c=0; return c; }. - Or let the caller own the storage and pass its address in.
- Or (advanced) allocate on the heap so it outlives the frame.
Recall Feynman check: one sentence per idea
Declaration promises the shape; definition builds the one real machine; a call hands over photocopies of your numbers; and a pointer is a house address you copy so the machine can walk back and edit your real thing.
Connections
- Pointers in C — the escape hatch from call by value.
- Stack and Function Call Mechanism — why locals die on return.
- Header Files and Separate Compilation — one prototype, many files.
- Arrays as Function Arguments — why arrays aren't copied.
- Scope and Lifetime of Variables — dangling pointers (Ex 5.2).
- Recursion — many frames of the same function at once.