Functions Debugging Exercises (10)
← Module home · Find → Fix → Reflect. Trace on paper before compiling — most function bugs are visible without a compiler. Hints are progressive.
D1 — The vanishing definition
#include <iostream>
double triple(double x); // the only mention of triple
int main() {
std::cout << triple(4) << '\n';
return 0;
}
Compiles cleanly… then the linker refuses. What does it say, and what’s missing?
Hints
- The compiler believed the prototype. Who checks promises after compilation?
- Gallery F1 — declared, never defined.
Fix: add the definition double triple(double x) { return 3 * x; }. Reflect: compiler = per-file grammar; linker = promise-keeping across the program.
D2 — The silent non-call
void banner() {
std::cout << "=========================\n";
}
int main() {
banner; // "call" the banner
std::cout << "Program started\n";
return 0;
}
No banner appears, and yet: no error. Why?
Hints
- Look at the call very closely — what’s missing?
- A function name without parentheses is a thing in C++ (gallery F3).
Fix: banner(); — calls need parentheses. Reflect: the bare name is an address expression, not an instruction; C++ happily evaluates and discards it.
D3 — The mutated nothing
void zeroOut(int n) { n = 0; }
int main() {
int errors = 5;
zeroOut(errors);
std::cout << errors << '\n'; // programmer expects 0
return 0;
}
Prints 5. The function ran — trace exactly where the 0 went.
Hints
- Draw the two boxes at the moment of the call.
- Which of them does
n = 0write into? - Copy rule — gallery F5.
Fix: the intent is write-back → void zeroOut(int& n). (Or better: int zeroed() { return 0; } / assign in caller — a function whose only job is “set to 0” barely deserves to exist.) Reflect: value parameters are for reading; writing back needs & or a return.
D4 — The prototype that lied
int areaOf(int side);
int main() {
std::cout << areaOf(3.7) << '\n'; // prints 9 — where did 3.7 go?
return 0;
}
int areaOf(int side) { return side * side; }
3.7 became 3 silently. Diagnose and give two fixes with different trade-offs.
Hints
- The argument’s type vs the parameter’s type.
- Implicit conversion — 3.7 → 3 at the call (Foundations Lesson 4).
- Gallery F6: mismatch by conversion, not by error message.
Fix (a): double areaOf(double side) — honest for real-valued sides. (b): keep int and make the caller truncate explicitly: areaOf(static_cast<int>(side)) — loud, reviewable. Reflect: silent conversions at call sites are the quiet way arithmetic goes wrong; make truncation a decision, not an accident.
D5 — The global gang
int total = 0; // "just this once"
void add(int amount) { total += amount; }
void reset() { total = 0; }
void report() { std::cout << total << '\n'; }
int main() {
add(50); add(30);
report(); // 80 — fine...
reset();
report(); // 0 — but WHY is it 0? who asked?
return 0;
}
The program “works”, yet the diagnosis is still: design bug. Explain it in two sentences and de-globalise.
Hints
- Who can change
total? (Everyone.) Who must know when it changes? (Everyone.) - Lesson 2 §3 — hidden state.
int add(int total, int amount) { return total + amount; }
// main: total = add(total, 50); total = add(total, 30); ...
Reflect: the fixed version can be tested in one line (check(add(0, 50), 50, ...)) — the global version needs a ritual before every test.
D6 — The half-traced ladder
int band(int score) {
if (score >= 80) return 'A' - '0'; // hmm
if (score >= 60) return 2;
// ... more bands ...
}
Wait — return 'A' - '0';? That’s 17. This one’s a reading exercise: list every problem you can find in this function before compiling anything. (There are at least three.)
Hints
- Return type vs what’s returned.
- Missing return paths — which scores reach the end?
- Would a
charreturn type serve better?
Fix: the honest version is the S5 ladder: char band(int score) with a return for every path. Reflect: three real bugs in eight lines (type mismatch, arithmetic on chars as magic numbers, missing returns) — and none of them is a syntax error. Compiling is not reviewing.
D7 — The ambiguous greeter
void greet(std::string name);
void greet(std::string name, std::string prefix = "Hello, ");
greet("Ayesha");
Compile error: call of overloaded greet is ambiguous. Walk the compiler’s dilemma out loud, then fix two ways and say which you’d ship.
Hints
- Both signatures can serve one argument — how?
- Defaults + overloads trap.
Fix (a): delete the one-arg overload (default covers it). (b): delete the default, keep both overloads with distinct meanings. Ship (a): one mechanism per optional (Lesson 4 §2).
D8 — The reader that ate the menu
int readChoice() {
int c;
std::cin >> c;
if (std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000, '\n');
return readChoice(); // "try again"
}
return c;
}
Type abc twenty times fast, and… the program freezes. (This uses recursion — a function calling itself — which you haven’t formally met; treat it as “the function restarts itself.”) What’s the risk in this pattern, and what loop-shaped fix do you already know?
Hints
- Each failed attempt * stacks* another unfinished function call — the stack grows.
- The validation gallery solved re-prompting with a loop, not restarts.
int readChoice() {
int c;
std::cin >> c;
while (std::cin.fail()) {
std::cin.clear();
std::cin.ignore(1000, '\n');
std::cout << "Numbers only — try again: ";
std::cin >> c;
}
return c;
}
Reflect: same behaviour, constant memory. Recursion is a real tool (later units) — but “repeat” usually means loop, and inside a reader function, stack-per-keystroke is a freeze waiting for a bad day.
D9 — The function that prints its answer
double applyDiscount(double amount) {
if (amount >= 1000) {
std::cout << "Discount applied!\n";
return amount * 0.90;
}
return amount;
}
Tests of applyDiscount print surprise lines, and the ⭐⭐ extension in the labs wants the discount without the message. Diagnose; refactor into the testable shape.
Hints
- Gallery F8 — compute and print in one machine.
- Who should own the message — and when?
double applyDiscount(double amount) { // pure, silent, testable
return (amount >= 1000) ? amount * 0.90 : amount;
}
// caller:
if (amount >= 1000) std::cout << "Discount applied!\n";
std::cout << applyDiscount(amount) << '\n';
(The condition is cheap to repeat; purity is worth it. Alternatively return the surcharge — 0 means no discount — and let the caller print accordingly.) Reflect: the message is a policy of the display, not a fact of the arithmetic.
D10 — The order-dependent orchestra
void partA() { std::cout << partB(2) + 1; } // uses partB...
void partB(int x) { std::cout << x * 10; } // ...defined BELOW
int main() { partA(); return 0; }
Compile error. Two legal fixes; which one matches the house layout, and why is the other one a trap at scale?
Hints
- The compiler reads top-down — what does it know when it reaches
partA? - Prototypes.
Fix (house style): add int partB(int x); to the prototypes block. (Trap fix): physically reorder definitions (partB above partA) — works now, and shatters the next time anyone adds a function; ordering gymnastics don’t scale, a table of contents does. Reflect: prototypes make definition order irrelevant — that’s their whole job.
Fix-list recap
| D | Bug family | Gallery |
|---|---|---|
| D1 | declared, never defined | F1 |
| D2 | call without parentheses | F3 |
| D3 | expecting write-back from a copy | F5 |
| D4 | silent argument conversion | F6 |
| D5 | mutable global state | F9 |
| D6 | type mismatch + magic numbers + missing returns | F7 (+review discipline) |
| D7 | overload/defaults ambiguity | F10 |
| D8 | recursion where a loop belongs | design |
| D9 | compute-and-print coupling | F8 |
| D10 | definition-order dependence | F2 (prototypes) |