← Daily LogFIELD LOG No. 037 — 15 Jul 2026

The linked list drill that found two new bugs

A pre-Milestone warm-up on linked lists — walking the list, delete_node — turned into six rounds of debugging and two new formalized mistake patterns, one of them caught only by AddressSanitizer.

ProgramDriver Development — Phase 1: C Programming
Session#37
Resultdelete_node: 6 rounds → clean under -Wall -Wextra + ASan
ProgressPhase 1: 257/257 (100%) · Total goal: 49.5%

What I got through

  • Traced deleting a middle node — the mechanism (prev/current tracking, relinking) was right and unaided, but I didn't actually spell out the free() step or the concrete final list state until I forced myself to re-trace it explicitly.
  • Traced deleting the head node — correctly spotted that prev == NULL signals the special case, since the loop never runs at all. But I described the update as "increment head" instead of the real *head = current->next reassignment, and mixed up the function's struct node **head parameter with the caller's own struct node *head variable. Took two hand-drawn diagrams to untangle.
  • Worked through what a single-pointer version would do to the caller's head on my own — got it right unaided, which was the real evidence the double-pointer mechanism had actually landed, imprecise trace wording aside.
  • Wrote delete_node itself — six rounds. Round one was a plain compile error. Round two compiled clean but hid a real bug: two independent if blocks let a second block re-touch a node the first one had already freed — a confirmed heap-use-after-free that still printed the correct list on a plain run, only caught because I ran it under AddressSanitizer for the first time this curriculum. Rounds three and four repeated the same stray-token typo. Round five restructured to else if, which fixed that bug but exposed a new one — prev == NULL alone doesn't tell a head-match apart from an empty list. Round six combined both conditions and passed all five cases (head, middle, tail, not-found, empty) clean.

Mistakes I made

  • Wrote two separate top-level ifs where I meant "exactly one of these should run" — nothing about that shape actually enforces exclusivity, so the second block ran anyway on freed memory.
  • Treated prev == NULL as sufficient proof of a head-match, when it's only necessary — an empty list produces the identical state.

What's next

This closes out the linked list portion of the pre-Milestone drill queue. Still pending before the actual Milestone Test: a leftover post/pre-increment trace question, a precision pass on a couple of older nuance errors, and two light confirmation passes.

Field note

AddressSanitizer got its first real use in this curriculum this session. Plain gcc -Wall and eyeballing the output would have missed the use-after-free entirely — it only looked wrong once something actually inspected the memory.

← 09 Jul 202616 Jul 2026 →

Notes from readers

Comments — via GitHub