Reproduce
Make undefined behavior visible without pretending that one observed output is guaranteed.
Engineering evidence, not guessesA repeatable C++ lab that turns a delayed heap-use-after-free into a verified cause, a minimal ownership fix, and a permanent regression check.
PurposeVivek Bhadra’s article explains why one tool cannot answer every debugging question. We turned that principle into a public lab where each tool has a precise job.
Make undefined behavior visible without pretending that one observed output is guaranteed.
Separate the late symptom from the earlier lifetime-ending event that caused it.
Prove the smallest correction with sanitizers and regression tests—not confidence alone.
The defectconst Reading* cached_reading = nullptr; void cache_for_dashboard(const Reading& reading) { cached_reading = &reading; }
void retire_reading(std::unique_ptr<Reading>& reading)
{
reading.reset();
}
// later, elsewhere...
const Reading& reading = *cached_reading;
A non-null address proves only that bits remain in the pointer. It does not prove that a live object still occupies that address.
The misleading symptomThe dashboard cache should retain the original reactor-inlet reading.
A later allocation can reuse the same heap address, making unrelated data appear in the dashboard.
cached object at 0x603000000040
new object at 0x603000000040
cached reading: id=9001, value=-273.15, label=unrelated-record
The repeatable workflowC:\amp-demos\cpp-debugging-demo> run_demo.bat ... SUCCESS: symptom, root-cause evidence, debugger inspection, and fix verified.
AddressSanitizermain()Where the object’s valid lifetime begins.retire_reading()The causal event: unique_ptr::reset() ends the object’s lifetime.render_dashboard()The visible symptom occurs away from the cause.That allocation → free → invalid-read chain is stronger than guessing from the crash line.
GDBbreak cache_for_dashboard break retire_reading break render_dashboard run print cached_reading continue backtrace full info locals
The correctionconst Reading* cached_reading;
cached_reading = &reading;
// Owner can destroy reading independently.std::optional<Reading> cached_reading;
cached_reading = reading;
// Cache retains a valid independent value.The corrected program prints id=42.
The corrected ASan target has no lifetime finding.
CTest makes the expected behavior repeatable.
What our work addsA small C++17 project readers can inspect, modify, and teach from.
A Windows batch file orchestrates the WSL build and all five verification stages.
Every run rebuilds the diagnostic targets, so the demonstration can be repeated safely.
A local browser view presents progress, confirmed findings, code changes, and terminal evidence.
The runner fails if expected sanitizer evidence or corrected tests are missing.
The lab explicitly teaches that the crash location and causal event may be far apart.
One-click dashboard
start_dashboard.bat. It selects a free local port and opens the browser—no WSL window is required.
Dashboard evidence
std::optional<Reading> gives the cache ownership of the retained value.
Run it yourselfgit clone https://github.com/zuwasi/cpp-debugging-demo.git cd cpp-debugging-demo run_demo.bat # or launch the visual dashboard start_dashboard.bat
Closing principleThe durable result of debugging is not merely an explanation. It is evidence, a justified correction, and an automated check that prevents rediscovery.