Skip to content
Courses

Find Where a Value Goes Wrong

Debugging FoundationsLesson 4 of 575 minutesLessonBuilds on: Connect Source to Live Machine StateNo lab next

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.

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:

Terminal window
cd ~/underdunn-course/exercises/debugging
gcc -std=c11 -Wall -Wextra -Wpedantic -O0 -g sum.c -o sum
./sum
gdb -q ./sum

Expect the ordinary run to print 6. Now we’ll observe whether the path to that result matches your prediction.

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 sum
run
next
print total
watch total
continue

The 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:

continue

Expect 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.

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 breakpoints
continue
quit

Answer y if asked to confirm deletion. This removes GDB breakpoints and watchpoints, not source files.

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.

  • total is not in scope: restart inside sum and 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 inspect total each 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: watch reports a value change, not every assignment. Use source stepping if the question concerns writes that preserve the value.

Knowledge check

Question 1 of 1

The first update changes total from 0 to 2. What does it tell you about replacement versus addition?