TL;DR
A real-world C++ production bug returned impossible API responses because two bool fields in a response struct were both true. The root cause was uninitialized primitive members in a non-trivial struct; the issue was detected by runtime sanitizers and addressed by forcing value-initialization or adding explicit initializers.
What happened
Years ago the author maintained a large C++ codebase for a payments API and received a report where an endpoint returned both "error": true and "succeeded": true — a logically impossible state. The Response struct had two bool fields and a std::string, and the function declared a local Response response; without an initializer. Because std::string makes the type non-trivial, the compiler emitted a default constructor that called default-initialization on members: the std::string was constructed but the primitive bools were left indeterminate. At runtime those uninitialized booleans could contain arbitrary values, so both fields could read as true. The author fixed the immediate call site by using value-initialization (Response response{};) and recommended zero-initializing locals. Alternatives discussed included adding a default constructor or in-class member initializers.
Why it matters
- Uninitialized primitive members in structs can produce incorrect behavior in production even when code looks straightforward.
- C++ initialization rules are subtle: non-trivial types trigger generated constructors that may leave primitive members indeterminate.
- Compilers do not always warn; runtime sanitizers and static analyzers have different coverage and limitations.
- A simple initializer at the declaration site can prevent hard-to-find bugs and reduce risk in critical systems (e.g., payments).
Key facts
- The bug produced contradictory API responses (both error and succeeded true) in a payments service handling large volumes.
- Response contained bool error, bool succeeded and std::string data; std::string made the struct non-trivial.
- Declaring Response response; invoked a compiler-generated default constructor that left primitive fields uninitialized.
- Using Response response{} forces value-initialization: booleans become zeroed and strings are default-constructed.
- Two safer fixes: implement an explicit default constructor that initializes every field, or supply in-class default member initializers (e.g., bool error = false).
- Clang with warnings did not flag the issue; clang-tidy could detect some instances (historically limited), cppcheck behavior was inconsistent.
- AddressSanitizer/UndefinedBehaviorSanitizer reported the runtime error: a load of an invalid value for type 'bool' was detected.
What to watch next
- Consider enabling runtime sanitizers (ASan/UBSan) on tests and CI to catch loads of invalid values at runtime.
- Adopt a code-style rule to value-initialize local aggregates (e.g., use T obj{} by default) to avoid indeterminate primitives.
- Be aware that adding explicit default constructors can trigger rule-of-(five/six) maintenance obligations (destructor, move/copy functions).
- not confirmed in the source
Quick glossary
- Default initialization: The initialization applied when an object is declared without an initializer; behavior depends on the type and can leave primitives indeterminate.
- Value initialization: Initialization performed with brace-or-equal {} syntax (e.g., T obj{}), which zero-initializes primitives and calls default constructors for objects.
- Non-trivial type: A type whose default construction, copy/move, or destruction requires user-defined behavior; compilers generate constructors differently for such types.
- Undefined behavior: Program behavior not prescribed by the language specification; reading uninitialized memory is an example and can produce unpredictable results.
- AddressSanitizer / UndefinedBehaviorSanitizer: Runtime tools that detect memory errors and certain undefined behaviors during execution, useful for catching issues missed at compile time.
Reader FAQ
Why did both bool fields become true?
Because the bool members were never initialized; in that context the compiler-generated constructor left primitive fields indeterminate and they could read as true at runtime.
How was the bug fixed quickly?
The author changed the declaration to Response response{};, forcing value-initialization so the booleans were zeroed and the string constructed.
Could the compiler have warned about this?
not confirmed in the source
Do uninitialized fields always cause undefined behavior?
Reading an uninitialized value is undefined behavior; merely having uninitialized fields is not UB until they are read.
⏴ Back to all articles Published on 2025-12-27 The production bug that made me care about undefined behavior C++ Undefined behavior Bug Table of contents The bug report Investigating Just…
Sources
- A production bug that made me care about undefined behavior
- The production bug that made me care about undefined …
- C++ programmer's guide to undefined behavior: part 7 of 11
- Erroneous behaviour for uninitialized reads
Related posts
- Responding When Users Publicly Say They Hate Your Product: A Playbook
- USPS finalizes DMM rule redefining postmark dates and possession
- Left Behind: Futurist Fetishists, Prepping and Earth’s Abandonment