C++ / Editorial solution guide
Repair dangling references and use-after-free
Find the owner behind a pointer, reference, or view, then redesign the lifetime relationship instead of relying on text that happened to remain in dead storage.
Read-only knowledge base ยท No public comments
Recognize the symptoms
A reference appears to work in one build but fails after optimization or a harmless edit. A memory diagnostic may report heap-use-after-free, stack-use-after-return, or another lifetime error depending on the case and enabled instrumentation. Those labels identify a class of access; they are not a portable prediction of how invalid code behaves.
Stale text, crashes, and apparently correct results can all accompany undefined behavior. Do not run an intentionally dangling example to discover its expected output. Investigate the creation, ownership, destruction, and later use of the underlying object instead.
Understand the likely causes
- References, raw pointers, string_view, and span ordinarily borrow storage rather than owning its lifetime. Returning a reference to a local, saving a view of a temporary, destroying a smart-pointer owner, or reallocating a container can end the validity of that borrowing relationship. const does not turn borrowed data into owned data.
- A stored callback can retain a reference capture after its creating scope ends. A shared_ptr copied independently from a raw address is not a valid repair either: it creates another ownership control block and can cause double deletion. Ownership must be established and transferred consistently.
Trace the owner and the final valid access
Write down which object owns the bytes and the exact event that destroys or relocates them. This deliberately broken excerpt returns a reference whose local owner dies on return. It is non-runnable and has no defined output. A debugger address remaining readable does not extend a C++ object lifetime. Review callers too, especially any that store the returned reference or wrap it in a view.
#include <string>
const std::string& broken_label() {
const std::string local{"report"};
return local;
}
Do not execute this function. The returned reference dangles after local is destroyed. No output is asserted.
Return a value for newly created data
Return std::string by value when the function creates a new string. The caller then owns the result independently, and ordinary copy elision or moving can support efficient transfer. Correct ownership is the reason for this change; avoid returning a local reference as a speculative optimization. The complete corrected program establishes a result that remains alive through printing and is released automatically afterward.
#include <iostream>
#include <string>
std::string label() { return std::string{"report"}; }
int main() {
const std::string owned = label();
std::cout << owned << "\n";
}
report
owned has its own lifetime in main. The function does not expose a reference to a destroyed local.
Repair the relationship at asynchronous boundaries
For queued callbacks, capture an owned value or an explicitly shared owner when work must retain the resource. Use weak_ptr plus a checked lock when work should tolerate expiration. For container elements, reacquire access after structural changes or store a stable logical identifier. Do not add shared ownership everywhere: a simpler scope or value return often removes the lifetime dependency entirely.
Verify the fix
Compile and run only the corrected complete program; its line is report followed by a newline. Add tests that use returned values after the creating function exits and that destroy original owners before optional observers run.
On a supported toolchain, run corrected application paths with AddressSanitizer in a dedicated build. Keep the ownership review even after a clean run: diagnostics check executed paths and may not detect every dangling use. No undefined example in this guide needs execution.
Keep it from recurring
- For every pointer, reference, or view that escapes a call, document its owner and invalidation conditions. Prefer owning return values, RAII members, explicit captures, and short borrowing scopes. Treat constness and lifetime as separate properties.