July 13, 2026·4 min read·engineeringcppstandardssafety

Classifying undefined behavior before changing C++

P3100R7 separates the cost of detecting undefined behavior from the question of what a program can do after detection.

A plan to reduce undefined behavior needs to distinguish failures the compiler can check at one operation from failures that require a history of the program. Timur Doumler and Joshua Berne make that distinction central to P3100R7. The paper is dated June 1 and appears in the July mailing. It proposes a systematic framework targeting C++29, with an inventory of explicit core-language undefined behavior and a classification of possible remedies.

That approach appeals to me because C++ already asks its readers to understand several eras of the language. Safety work needs to describe exactly which obligations change. A claim that a language is becoming safer is less useful than an account of the operation, the information needed to check it, and the behavior after a violation.

The route changed when the white paper stalled

The proposal's history reaches back to an initial version in 2023. Its account describes support in 2025 for a core-language undefined-behavior white paper. The intended process was incremental approval of material into that document. Per-clause review calls were never scheduled, and the authors report that work on that publication route stalled.

They subsequently redirected the proposal toward C++29. The March 2026 Croydon poll asked them to apply the rules to runtime-checkable cases and return with full wording for case-by-case review. Revision 7 follows the completed wording work in revision 6 and responds to early wording review.

This history explains why the paper contains both a broad taxonomy and detailed specification changes. The classification provides a basis for deciding which remedy fits each case. The wording makes those choices reviewable as actual language rules. Neither subgroup encouragement nor completed proposed wording means that the framework has already become part of a published standard.

An inventory needs a stated boundary

The authors enumerate 80 cases of explicit core-language undefined behavior, using stable identifiers and ten categories. Their count follows occurrences in specification wording. They acknowledge that another granularity could split a single sentence into several distinct failure mechanisms. The count is consequently an inventory of the specified cases under that method, not a measurement of how often bugs occur.

Their scope excludes library undefined behavior, compile-time and link-time issues, and behavior left undefined by accidental omissions in the specification. The related P3471R4 library hardening proposal describes a different route for selected library preconditions. Keeping that boundary explicit prevents the language inventory from being mistaken for a catalogue of every unsafe operation in a C++ program.

This is a sound discipline for safety discussions. A percentage of wording cases cannot establish a percentage of vulnerabilities prevented. A small number of rules can govern operations executed very frequently. Another rule can describe an unusual path with serious consequences. The taxonomy helps choose mechanisms, while prevalence requires evidence from actual software.

Cheap checks depend on available information

P3100R7 separates static diagnosability, locality and runtime cost. A null pointer can be identified from the pointer value. A pointer to an object whose lifetime has ended requires additional information if its value still looks like an ordinary address. Those are different checks, despite both involving an invalid pointer.

The paper identifies 19 cases that are unconditionally locally diagnosable at runtime. Most of its cases require information beyond the local operation in the general case. Its examples of additional tracked information include whether storage has been freed and whether an object's lifetime has ended.

That distinction explains why adding a condition to an operation can be straightforward in one case and expensive in another. A comparison against zero already has the data it needs. A lifetime check needs a trustworthy account of allocation and destruction. Producing and maintaining that account is part of the cost, even if consulting it is inexpensive.

RAII is still the strongest argument for C++ in my view. Scope-bound cleanup gives ownership an implementation mechanism, but aliases can still outlive the owned object. The framework's separation of local checks from lifetime tracking makes clear why cleanup alone cannot establish that every later access through an alias is valid.

Detection and replacement behavior answer different questions

The earlier P2795R5 describes erroneous behavior for uninitialized reads. Its design tries to keep an operation recognizable as a programming error while constraining its consequences. P3100R7 considers that technique alongside runtime checks and other tools, including language restrictions and annotations.

Detecting a violation does not automatically supply a useful value with which execution can continue. The program may need a result, and the type of that result can limit the available replacement behavior. Conversely, an operation might admit a constrained erroneous result while still needing a diagnostic to tell the author that the computation is invalid.

That separation matters for operational expectations. A program that continues after a diagnosed error may still produce an incorrect business result. Constraining language behavior provides a foundation for reasoning about the failure. It does not establish that the application can accept the result or keep serving a request normally.

Contracts provide a policy mechanism

The framework builds on P2900R14 contracts. That design separates assertions from their evaluation semantics and provides violation handling. P3100R7 proposes implicit contract assertions for the relevant language conditions, with replacement behavior where possible and an escape mechanism for performance-sensitive cases.

The useful policy question is then specific. Which condition is checked, what information supports the check, and what does the selected evaluation policy do on failure? Those questions can be reviewed for one operation without pretending that all undefined behavior has the same cause or cost.

As of the July publication, this remains a proposal for further committee review. Its practical contribution is a structured explanation of the tradeoffs. Local checks can address some failures without changing source code. More general lifetime and type guarantees need additional mechanisms, and continued execution after a violation needs separately specified behavior. Those distinctions give the C++29 discussion concrete choices to evaluate.