Find Where a Value Goes Wrong
The last value isn’t the whole explanation
Section titled “The last value isn’t the whole explanation”A program is supposed to add 2, 4, and 6, but it prints 6. Perhaps it skipped two records. Perhaps it added everything and then replaced the answer. Both stories could explain the last number, so that number alone doesn’t let us choose between them.
Let’s ask a narrower question: after which update does the running total first disagree with the sum it should represent? This lesson uses the loops, arrays, scope, and stepping you’ve already practiced. We won’t change the code until the observations tell us what needs changing.
Predict each update
Section titled “Predict each update”Save this complete example as sum.c in the debugging exercise directory:
#include <stdio.h>
int sum(const int *values, int count) { int total = 0; for (int i = 0; i < count; i++) { total = values[i]; } return total;}
int main(void) { int values[] = {2, 4, 6}; printf("%d\n", sum(values, 3)); return 0;}Write two predicted sequences: the totals required by addition, and the totals this particular source will produce. Keep them separate. The first describes intended behavior; the second is your hypothesis about the implementation.
Shell input:
cd ~/underdunn-course/exercises/debugginggcc -std=c11 -Wall -Wextra -Wpedantic -O0 -g sum.c -o sum./sumgdb -q ./sumExpect the ordinary run to print 6. Now we’ll observe whether the path to that result matches your prediction.
Watch an initialized value
Section titled “Watch an initialized value”A breakpoint stops at a location you choose. A watchpoint stops when a chosen expression’s value changes, so you don’t have to guess every place that might update it. We’ll watch one local accumulator while its function is active.
GDB input:
break sumrunnextprint totalwatch totalcontinueThe first next executes the initialization, so total is zero before we watch it. Without that step, an apparent initial value could simply be whatever occupied the storage before initialization.
The first watchpoint stop should report an old value of 0 and a new value of 2. The first record therefore doesn’t distinguish replacement from accumulation: both operations produce two when the original total is zero. A plausible wrong implementation has survived its first test.
Predict the next update, then enter:
continueExpect old value 2, new value 4. The intended running sum would now be 6, so this is the first disagreement. Continue once more to observe 4 becoming 6.
Notice the timing of the evidence. The watchpoint reports a change that has happened; the source line displayed at that stop may be the next line after the update. That’s different from assuming a breakpoint’s displayed statement has already executed. Read the old and new values with the surrounding source.
Explain the first disagreement
Section titled “Explain the first disagreement”The loop visits all three values, but total = values[i] replaces the previous total. At the second update, it stores four instead of adding four to two. That explains both the first divergence and the final output.
We didn’t establish this merely because an assignment looked suspicious. We predicted its transitions and checked them against the running program. With a larger function, that comparison is what prevents you from fixing the first unusual-looking line and declaring victory.
GDB normally uses hardware support for a simple watchpoint when the guest provides it. A software watchpoint can do the same value comparison by stepping, but is slower. The watchpoint reference also explains why a local watchpoint cannot outlive its scope. A plain watch total watches value changes; it does not promise a stop for a write that leaves the same value behind.
When you’ve recorded the three updates, remove this session’s stopping points and let the example finish:
delete breakpointscontinuequitAnswer y if asked to confirm deletion. This removes GDB breakpoints and watchpoints, not source files.
Practice: change one record
Section titled “Practice: change one record”Copy sum.c to sum-practice.c. Change only the middle value from 4 to 9, then build a separate executable. Predict both the intended totals and the actual transitions before setting the watchpoint again.
Common failures and recovery
Section titled “Common failures and recovery”totalis not in scope: restart insidesumand execute its initialization before creating the watchpoint.- GDB cannot insert a hardware watchpoint: start a fresh session, enter
set can-use-hw-watchpoints 0, and repeat the same observation. This example is small enough for the software fallback. You can also step over the assignment and inspecttotaleach iteration. - A watchpoint disappears when the function returns: its local variable stopped existing. Restart and create a new watchpoint in the new call.
- A repeated value doesn’t stop:
watchreports a value change, not every assignment. Use source stepping if the question concerns writes that preserve the value.
Check your understanding
Section titled “Check your understanding”Knowledge check
Question 1 of 1
Practice complete
You've checked the key ideas. Revisit any explanation you need, then continue when it makes sense.