5.1.7 · D4C Programming

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

2,684 words12 min readBack to topic

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, passing 3 and 9. 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 value 10 into the parameter x. Think of x as a fresh sticky note that starts life reading 10.
  • Inside, x = x + 5 changes that sticky note to 15, and it is returned → r = 15.
  • k back in main is a different piece of memory; nothing touched it, so k is still 10.

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) copies 7 into n. Setting n = 999 scribbles on the copy only. When the call ends, that copy (and its whole stack frame) is destroyed. So a prints 7.
  • tryChange used without () is the function's address, which is never zero, so tryChange ? 1 : 0 is 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.

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

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:

  • &p is p's house number. We copy that number into the parameter price. (Still call by value — we copied an address value! — but the address points home.)
  • *price means "the double stored at that house" — i.e. the real p in main.
  • *price = *price * 1.1 computes 200 * 1.1 = 220 and writes it into the caller's p.

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.
  • minmax needs to hand back two numbers; a function returns one value, so the extra outputs go through pointer parameters outMin, outMax. *outMin = mn writes straight into main's lo.
  • The array v decays 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.