#Vulnerabilities

Why Undefined Behavior Is Everywhere in Modern C/C++ Code

Trends Reporter
6 min read

The article argues that undefined behavior (UB) pervades even well‑written C and C++ programs, from alignment issues to subtle standard violations, and that relying on human review alone is no longer sufficient. It explores concrete examples, explains why compiler assumptions make UB dangerous regardless of optimization levels, and discusses how tooling—especially AI‑assisted analysis—might be the only realistic way to keep legacy codebases safe in 2026.


The claim: All non‑trivial C/C++ code contains undefined behavior


The author, a veteran of three decades of C and C++ development, contends that the language specifications are riddled with traps that turn everyday code into UB. The argument is not that every line is malicious, but that the possibility of UB is baked into the semantics of the languages and that modern hardware and compiler optimizations amplify the risk.


How UB sneaks in – concrete patterns

Category Typical code pattern Why it is UB (standard reference) Real‑world impact
Misaligned access int foo(const int *p) { return *p; } called with a non‑aligned pointer C23 6.3.2.3 – accessing an object via a pointer that does not meet the required alignment is undefined. On Alpha the kernel may emulate the load, on SPARC you get SIGBUS, on x86 it often works, but future ISAs may trap or silently corrupt data.
Atomicity on unaligned objects std::atomic<int>* p; p->store(123); where p is not aligned Same rule as above; the standard does not guarantee atomicity for misaligned atomic objects. May appear atomic on x86 but break on ARM or RISC‑V, causing subtle race conditions.
Pointer casting without provenance bool parse_packet(const uint8_t* bytes) { const int* magic = (const int*)bytes; … } The cast discards the original object’s effective type; using the resulting pointer to read violates the strict‑aliasing rule (C23 6.5). May crash on SPARC, work on x86, or trigger hardware‑specific side effects on embedded systems.
Library function misuse bool bar(char ch) { return isxdigit(ch); } isxdigit expects an int value representable as unsigned char or EOF. Passing a signed char with a negative value leads to out‑of‑bounds array indexing inside a naïve implementation. Could read from I/O‑mapped memory, potentially activating hardware in an embedded context.
Floating‑point to integer conversion int ms = (int)(seconds * 1000.0); C23 6.3.1.4: conversion is UB if the result cannot be represented, including when seconds is NaN or infinite. Silent overflow, undefined results, or crashes on platforms that raise exceptions for invalid conversions.
Null‑pointer dereference masquerading as a valid address Placing an object at address 0 and calling through a pointer that equals NULL. C23 6.3.2.3: dereferencing a null pointer is UB, regardless of the actual hardware address. Compilers may assume the call never happens and optimise away the call, leading to missing functionality or crashes.
Variadic function mismatches execl("/bin/sh", "sh", "-c", "date", 0); The final argument must be a char * null pointer, not an integer literal 0. Undefined behaviour may cause the argument list to be mis‑interpreted, leading to security‑critical failures.
Format string type mismatches printf("%ld\n", uid); where uid is uint64_t. The format specifier does not match the argument type; the behaviour is undefined (C23 7.21.6.1). May print garbage, truncate values, or even cause memory corruption on some ABIs.

These examples illustrate a pattern: the language permits a programmer to write code that looks correct, but the standard leaves the compiler free to assume the “bad” path never occurs. The result is that the same source can behave differently across architectures, compiler versions, or even optimisation flags.


Why turning off optimisation does not protect you

Many developers believe that compiling with -O0 eliminates UB‑related crashes because the optimizer is the “culprit”. The reality is more subtle:

  1. UB is a contract, not a optimisation trick – The compiler is allowed to assume that UB never happens, regardless of optimisation level. It may generate straightforward code that still relies on that assumption.
  2. Code generation still follows the abstract machine – Even at -O0 the compiler translates each expression according to the language rules, which may include undefined side‑effects (e.g., signed overflow).
  3. Link‑time and library code – UB can surface when linking against pre‑compiled libraries that were built with aggressive optimisations.

Therefore, the safety net of “no optimisation” is a mirage; the underlying contract with the compiler remains unchanged.


Counter‑perspectives: Is the problem that bad?

1. “We’ve lived with UB for 40 years – why panic now?”

The historical argument is that most production code runs fine on the dominant x86/AMD64 platforms, so the risk is acceptable. However, three trends weaken that comfort:

  • Diverse deployment targets – IoT, ARM‑based servers, RISC‑V, and niche embedded CPUs are now mainstream. Each has its own alignment and atomicity guarantees.
  • Security‑critical environments – Modern threat models exploit UB (e.g., Spectre‑style attacks) to achieve arbitrary code execution.
  • Regulatory pressure – Standards such as SOX or IEC 61508 increasingly demand demonstrable absence of undefined behaviour in safety‑critical software.

2. “Static analysis tools already catch most UB”

Static analysers (Clang‑tidy, Coverity, PVS‑Studio) indeed flag many patterns, but they have limits:

  • False negatives – Complex aliasing, indirect pointer arithmetic, or custom hardware intrinsics can evade detection.
  • Scalability – Large codebases generate thousands of warnings; triaging them manually is costly.
  • Evolving standards – New UB introductions in C23/ C++23 outpace tool updates.

3. “Just rewrite in Rust or another safe language”

Porting an existing codebase to a memory‑safe language is rarely feasible for legacy systems that interact with hardware, OS kernels, or have performance constraints. A hybrid approach—isolating safety‑critical modules in Rust while keeping the bulk in C/C++—helps, but the majority of the code still needs UB mitigation.


Toward a pragmatic solution: AI‑assisted UB sanitisation

The author’s experiment with a large language model (LLM) that scans C code and reports UB is promising. A realistic workflow might look like this:

  1. Automated UB detection – Run an LLM‑powered static analyser on each commit. The model can surface obscure UB (e.g., the isxdigit sign‑extension case) that traditional tools miss.
  2. Human‑in‑the‑loop verification – A senior engineer reviews the suggested fix, confirming that the change does not alter intended semantics.
  3. Automated regression testing – After applying the patch, the CI pipeline runs a full test suite on multiple target architectures (x86, ARM, RISC‑V).
  4. Documentation of assumptions – The tool generates a short comment explaining the original UB and the rationale for the fix, making future maintenance easier.

While this process still requires expert time, it concentrates effort on the most dangerous spots rather than a blanket rewrite. Moreover, the LLM can be fine‑tuned on a project’s own codebase, reducing false positives over time.


Bottom line

Undefined behavior is not a rare edge case; it is woven into the fabric of C and C++ semantics. The fact that a program “appears to work” on one platform does not guarantee correctness elsewhere, nor does it protect against future compiler changes. Turning off optimisation, relying solely on classic static analysers, or hoping that legacy code will magically stay safe is insufficient.

The community’s emerging response—AI‑augmented analysis combined with disciplined human review—offers a scalable path forward. Until such tooling becomes ubiquitous, treating C/C++ development as a compliance‑critical activity (e.g., a SOX‑level control) is a prudent stance.


Further reading


This article reflects a pattern‑spotting view of the current state of C/C++ development, highlighting both the pervasiveness of UB and the emerging tools that may finally let us manage it at scale.

Comments

Loading comments...