C++26 Declares War on Undefined Behavior with 'Erroneous Behaviour' for Safer Code

If you've attended a C++ conference lately, you’ve likely heard the murmurs—safety isn't just a buzzword; it's a battleground. Undefined behavior (UB), particularly from uninitialized variables, has long haunted developers with its capricious nature, causing everything from subtle bugs to catastrophic crashes. As one engineer recounts, eliminating UB was pivotal when scaling services tenfold: "One main point for us was to remove uninitialized variables which often lead to crashing services." Now, C++26 is set to turn the tide with a groundbreaking change: erroneous behaviour.

The Undefined Behavior Quagmire

Undefined behavior occurs when programs read uninitialized variables, leading to unpredictable results—compilers might optimize code into oblivion, or systems might fail mysteriously under load. This isn't just a safety hazard; it erodes stability in critical applications. For years, tools like Clang and GCC offered band-aids, such as -ftrivial-auto-var-init=zero, to initialize variables by default. But these lacked diagnostics, leaving developers in the dark about where initialization was missed. As Sandor Dargo notes, "You don’t have to systematically go through your code... which you probably wouldn’t do anyway."

Enter Erroneous Behaviour: A Defined Path to Safety

With P2795R5 by Thomas Köppe, C++26 reclassifies uninitialized reads as "erroneous behaviour"—a well-defined, yet incorrect, state. Compilers are encouraged to diagnose these issues via warnings or runtime errors, transforming UB from a silent killer into a loud alarm. For example:

void foo() {
  int d;  // d has an erroneous value
  bar(d); // Compilers should diagnose this!
}

Here, d isn't left to the whims of UB; it holds an implementation-specific value (e.g., a trap representation), and bar(d) triggers erroneous behaviour. Crucially, this requires no code overhaul—existing programs gain safety simply by recompiling. The standard’s "recommended" diagnosis might sound lenient, but as Dargo argues, "an implementation that wouldn’t diagnose erroneous behaviour would be soon out of the game" amid growing safety demands.

Opting Out with [[indeterminate]]

C++ preserves its zero-overhead principle with the new [[indeterminate]] attribute, allowing deliberate uninitialization for performance-critical scenarios. However, reading such variables reverts to classic UB, emphasizing developer intent:

void foo() {
  int d [[indeterminate]];  // Opt-out for performance
  bar(d); // Danger: Undefined behaviour!
}

This extends beyond simple variables. Structs and function parameters also fall under the new rules—uninitialized members taint entire objects, and parameters can be explicitly marked:

struct S {
  S() {}
  int num;
  std::string text;
};

void foo(S s1 [[indeterminate]], S s2) {
    bar(s1.num); // UB: Indeterminate value
    bar(s2.num); // Erroneous behaviour
}

Why This Matters: Safety as a First-Class Citizen

Erroneous behaviour isn't just a technical tweak; it's a cultural shift. By making uninitialized reads diagnosable, C++26 reduces the attack surface for memory-corruption vulnerabilities and enhances stability in systems demanding resilience—think cloud infrastructure or embedded devices. It also signals the committee's commitment to safety without abandoning C++'s legacy. As of early 2025, no compilers support this yet, but expect rapid adoption as tools like Clang and MSVC align with the standard.

For developers, this means fewer debugging nightmares and more robust code. But it also demands vigilance: embracing compiler warnings and auditing code for accidental uninitializations. In a world where software underpins everything from AI to aerospace, C++26's move isn't just progress—it's a necessary evolution toward trustworthy computing. After all, as the community converges on safety, undefined behavior’s days as a silent saboteur are numbered.

Source: Adapted from Sandor Dargo's analysis on C++26 Erroneous Behaviour.