Arrays Debugging Exercises (10)
← Module home · Find → Fix → Reflect. Draw the boxes and trace before compiling — array bugs live in the index arithmetic. Hints are progressive.
D1 — The sixth guest
// intent: print 5 marks
int marks[5] = {72, 85, 91, 67, 78};
for (int i = 0; i <= 5; i = i + 1) {
std::cout << marks[i] << ' ';
}
Five marks in — six values out, the last one nonsense (or a crash). Diagnose precisely.
Hints
- List the indices the loop visits.
- Which one is not a box? (bounds)
- Gallery A1.
Fix: i < 5. Reflect: size 5 → valid indices 0–4; <= is the fence-post error. The house i < n style exists because its exit sentence is “while the index is valid”.
D2 — The memory exhibit
// intent: initialize all 10 boxes to 1
int a[10];
for (int i = 1; i <= 10; i = i + 1) {
a[i] = 1;
}
std::cout << a[0] << '\n'; // surprise!
Two bugs share this loop. Find both, and explain what a[0] might print.
Hints
- Which boxes did the loop actually write?
- Which box did it write that isn’t a box?
- Box 0 was never initialized — gallery A6.
Fix: for (int i = 0; i < 10; i = i + 1) a[i] = 1; (or int a[10] = {1};? — no: that fills box 0 with 1 and the rest with 0 — the {v} shortcut only fills everything when v is 0. Say the initialization honestly: an explicit loop.) Reflect: this loop both started one too late (skipping box 0) and ended one too far (touching index 10) — the two ends of the same off-by-one coin.
D3 — The partial print
int data[100] = {0};
int n = 4;
// ... four values read into data[0..3] ...
for (int i = 0; i < 100; i = i + 1) {
std::cout << data[i] << ' ';
}
The output is 72 85 91 67 followed by ninety-six zeros. Why is using the capacity here wrong, and when would the same loop be right?
Hints
- Capacity vs logical size.
- What does the array mean after 4 values?
Fix: for (int i = 0; i < n; i = i + 1). Reflect: the same capacity loop is correct in the initialization pass (touch all boxes once) and wrong in every data pass (touch only the live boxes). Purpose decides the bound.
D4 — The search that found −1
int pos = linearSearch(marks, n, target); // returns -1 when absent
std::cout << "Your mark: " << marks[pos] << '\n';
Search for 999 (absent) → the program prints a “mark” that was never entered — or crashes. Trace the two failure modes.
Hints
- What is
poswhen the target is absent? - What does
marks[-1]mean? (Lesson 2 §1’s warning)
if (pos != -1) std::cout << "Your mark: " << marks[pos] << '\n';
else std::cout << "Not found.\n";
Reflect: a search result is data-as-index — every index from data must be validated before use (gallery A3).
D5 — The copy that wasn’t
int a[3] = {1, 2, 3};
int b[3];
b = a; // "copy"
std::cout << b[0] << '\n';
It doesn’t even compile. What does the compiler say, what would the honest copy be — and (advanced thought) why can’t arrays be assigned the way ints can?
Hints
- Gallery A4.
- An array name is a handle on the boxes, not a value.
for (int i = 0; i < 3; i = i + 1) b[i] = a[i];
Reflect: ints are values; array names are handles — assignment semantics differ precisely because of what the name is (Lesson 3 §1 foreshadows this).
D6 — The function that saw garbage
void printAll(const int data[]) { // note: no size parameter
for (int i = 0; i < 5; i = i + 1) // "we always have 5"
std::cout << data[i] << ' ';
}
int main() {
int data[5] = {0};
int n = readData(data, 5); // user entered only 2 values
printAll(data);
return 0;
}
With 2 values entered, the output is 72 85 0 0 0 — two real values and three zeros that mean nothing. The hard-coded 5 is one bug; the missing size parameter is the deeper one. Fix both properly.
Hints
- Who knows
n? Who needs it? - The size travels separately.
Fix: void printAll(const int data[], int n) with i < n; call printAll(data, n). Reflect: a function cannot ask an array its length — hard-coded sizes are a lie the first time input varies; the logical size is a parameter, always.
D7 — The const violation
int total(const int a[], int n) {
int t = 0;
for (int i = 0; i < n; i = i + 1) {
t += a[i];
a[i] = 0; // "clean up as we go"
}
return t;
}
The compiler rejects it. Quote the promise that was broken, then decide: is the “cleanup” ever a good idea — and where would it go if it were?
Hints
const int a[]promised what? (Lesson 3 §2)- Does summing need to mutate?
Fix: delete a[i] = 0; — summing is a pure read. If a genuine “consume as you read” task existed, the parameter would be a plain writer int a[] and the name would say so (consume(...)) — but hiding a write inside a function named total breaks the read/write contract that makes teams reviewable. Reflect: const isn’t bureaucracy; it’s the compiler enforcing your signature’s promise.
D8 — The tally that exploded
int tally[5] = {0};
for (int i = 0; i < n; i = i + 1) {
tally[answers[i]] += 1; // answers are "1..5", we think
}
With the real answer sheet containing one 7 and one 0, this sometimes works, sometimes crashes. Diagnose the index range, then fix with the guard from Lesson 2 §4 — and decide what to do with out-of-range answers (count separately? reject?).
Hints
- Which indices can
answers[i]produce? - Which are valid tally boxes?
- Data-as-index needs the range check inside the loop.
if (answers[i] >= 1 && answers[i] <= 5) tally[answers[i]] += 1;
else invalid += 1; // honest third category
Reflect: the tally is only as safe as the data’s range — the guard converts “sometimes works” into “defined behaviour with a documented outcome”.
D9 — The backwards copy
// intent: shift each box right by one: a[i] takes a[i-1]'s value (box 0 lost)
for (int i = 1; i < n; i = i + 1) {
a[i] = a[i - 1];
}
Intent: {4, 8, 15, 16} → {4, 4, 8, 15}. Actual: {4, 4, 4, 4}. Why does direction matter here, and which loop order fixes it?
Hints
- Trace box by box: when you write
a[2], what’s ina[1]— the original or an overwritten copy? - The boxes you still need are the ones you’re overwriting first.
Fix: run the loop backward — for (int i = n - 1; i >= 1; i = i - 1) a[i] = a[i - 1]; — so each box is overwritten only after it has been copied. Reflect: in-place transformations have a direction; when the source of one write is the destination of another, walk away from the reads. (C2 is the general workout.)
D10 — The mirrored matrix
// intent: print the transpose of a 3x3 matrix
int a[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
for (int r = 0; r < 3; r = r + 1) {
for (int c = 0; c < 3; c = c + 1) {
std::cout << a[r][c] << ' ';
}
std::cout << '\n';
}
std::cout << "transpose:\n";
for (int r = 0; r < 3; r = r + 1) {
for (int c = 0; c < 3; c = c + 1) {
std::cout << a[r][c] << ' '; // "just swap the loops"
}
std::cout << '\n';
}
The “transpose” prints the original. One character fixes it — which, and why does the same bug hide on symmetric test data?
Hints
- What does a transpose access —
a[?][?]? - Swapping the loops vs swapping the indices — which changes the access?
Fix: print a[c][r] — the transpose is an index swap, not a loop swap (gallery M1; swapping the loops only changes visit order, which for a full-grid print changes nothing). Reflect: this is exactly why E30/S30 demands asymmetric test data — on {1,2,3},{4,5,6}-style data the bug is visible instantly; on symmetric matrices it’s invisible.
Fix-list recap
| D | Bug family | Gallery |
|---|---|---|
| D1 | off-by-one bound | A1 |
| D2 | skip-first + touch-past-end | A1/A6 |
| D3 | capacity where logical size belongs | A2 |
| D4 | unchecked search index | A3 |
| D5 | array assignment with = |
A4 |
| D6 | missing size parameter / hard-coded size | A2 + Lesson 3 §3 |
| D7 | write through a const promise | Lesson 3 §2 |
| D8 | data-as-index without range guard | A3 |
| D9 | in-place shift in the wrong direction | direction discipline |
| D10 | swapped indices vs swapped loops | M1 |