Search for “C++26 memory safety” and the results won’t agree with each other. One post declares C++ finally memory-safe. The next, titled something like “C++26 memory features won’t save you,” says nothing important changed.
In reality, no one serious would believe either extreme. Everyone writing C++ knows the language isn’t memory-safe; that’s the reason an entire generation of languages, Rust chief among them, exists to solve that one problem. The latest standard revision is a step to alleviate but not fully solve it.
But between the victory laps and the dismissals, it is genuinely hard to get a straight answer to the only question that matters if you maintain an existing C++ codebase: what should I implement and use, and what does it get you?
tl;dr: Here’s the short version, because the search results won’t give it to you straight:
- Did C++26 make C++ memory-safe the way Rust is? No.
- Did the safety Profiles everyone was talking about land in C++26? No. They slipped to the next standard cycle and are currently targeted for C++29.
- So what actually shipped? Erroneous behavior for uninitialized variables, a hardened standard library, contracts, and a new async model.
- What can you turn on for an existing codebase right now? The first two. Both deliver real safety gains from a recompile alone, with no rewrites and no need to annotate your code by hand.
There is a gap between what dominated the headlines and what actually made it into the standard. And that is where the real story that’s worth understanding is. Let’s unpack it.
Why C++ safety became a hot topic this year
Memory safety used to be a topic for language-committee mailing lists, but now it is a line item in procurement.
The shift was heavily influenced by regulations. In 2023, CISA, the NSA, the FBI, and international partners published The Case for Memory Safe Roadmaps, which named C and C++ as memory-unsafe and asked vendors to publish a plan. In June 2025, the NSA and CISA followed with a joint guidance document on reducing memory-related vulnerabilities. The most widely cited expectation: by January 1, 2026, software makers who supply critical infrastructure should publish a memory-safe roadmap, meaning a written plan for how they will drive memory-safety bugs out of their existing products.
Why? There is data to support it. For years, both Microsoft and Google have reported that roughly 70% of their serious security vulnerabilities are memory-safety bugs. Those are cases where a program reads or writes memory it should not: using a chunk of memory after it has been freed, running past the end of an array, or reading a variable that was never assigned a value. That number is why regulators singled out the languages rather than the programmers.
Take note, however, that nobody is asking you to rewrite a million lines of C++ into Rust. The regulators know that is not realistic on any timeline that matters, which is why the ask is a roadmap, not a rewrite. The committee has been arguing the same question inside the standard itself, in a paper titled, plainly, Should C++ be a memory-safe language?.
For most teams, the honest expectation is incremental gains. Make the code you already have measurably safer without stopping the world. C++26 is the first standard that gives you real tools for that. They are just not the ones that made the news.
Regulators ask for a memory-safe roadmap, not a full rewrite. The realistic path keeps your existing C++ and hardens it in steps.
C++ profiles did not make the cut
We’re leading with the disappointment because it is what most of the internet gets wrong.
When people say “C++ memory safety,” they usually mean Profiles. A profile is a set of opt-in safety checks, grouped by category (bounds, lifetime, type), that a compiler can enforce across your whole codebase without you having to annotate every object by hand. It is the pragmatic, C++-flavored answer to Rust. You get to keep your code, switch on guardrails by category, and tighten them over time.
Unfortunately (or fortunately, depending on where your beliefs lie), Profiles did not make C++26. Bjarne Stroustrup, who has long pushed for them, saw it coming. In a May 2025 interview he warned that the committee “got confused and did not guarantee that this would be in C++ 26.” He was right.
When the standard was finalized at the March 2026 meeting, profiles were left out, as Herb Sutter’s trip report confirms. The work now continues toward the next cycle. Stroustrup’s type-safety profile paper (P3984) and a general profiles framework are both targeted at C++29, with no final ship date promised.
The other proposal didn’t make it either. Safe C++ (P3390) was the Rust-style proposal: a borrow checker, lifetime annotations, and provable memory safety for code written in the new dialect. (A borrow checker is the compile-time system Rust uses to prove your code never touches memory it should not.)
The committee weighed the two directions and picked profiles. Work on Safe C++ inside ISO was discontinued. The vote settled 19 for profiles, 9 for Safe C++, and 11 for both.
So the feature most likely to give you Rust-grade guarantees wasn’t accepted, and the borrow-checker alternative is off the table in ISO for now. If a blog told you C++26 shipped memory-safety profiles, you can treat that claim as outdated.
Two competing routes to memory safety. The committee chose Profiles over the Rust-style Safe C++, but neither made C++26.
What did ship, and what it’s worth on real coding
Now the good news. It is quieter than the headlines, but far easier to adopt. (Reflection, C++26’s other marquee feature, has its own story for large codebases; we dug into what it actually changes separately.)
The hardened standard library: the one thing you should know
If there is anything to integrate into your roadmap first, this should be it.
C++26 standardizes a hardened standard library (proposal P3471, “Standard Library Hardening”). In plain terms, it adds opt-in safety checks to the most-used bounded operations: vector::operator[], span, string, string_view, and dozens more.
Where an out-of-bounds index (reaching for element 100 in a 10-element container) was once silent undefined behavior, a hardened build turns it into a checked violation that stops the program at runtime, before anything worse happens.
std::vector<int> v = load(); int x = v[i]; // today: silent UB if i is out of range // hardened: precondition check traps before the bad read
The reason this is important is because of the field data. In Herb Sutter’s March 2026 trip report, Google’s production rollout of hardening fixed over 1,000 bugs, cut the segfault rate across their fleet by about 30%, and cost roughly 0.3% in performance.
A segfault is a crash that happens when a program touches memory it is not allowed to touch, and the “segfault rate across the fleet” is simply how often those crashes happen across all of Google’s running machines. So a 30% cut means nearly a third fewer of those crashes in production.
That last number, the 0.3%, is the one to sit with. Bounds checking has a reputation for being expensive, and here it measured as a rounding error against a 30% drop in crashes.
For a Visual Studio team, there’s a catch: you have to explicitly turn it on. The standard does not dictate how you enable hardening, so it arrives as compiler and build-system flags rather than one portable switch. MSVC has shipped bounds-check machinery for years through _ITERATOR_DEBUG_LEVEL and _CONTAINER_DEBUG_LEVEL, and recent MSVC builds have begun implementing the C++26 hardening model. The exact flag story is still settling as of this writing, so confirm the current behavior in your VS 2026 toolset before you promise anything to a security team. But the direction is set, and partial support is already here.
Uninitialized reads: from undefined to erroneous behavior
This one targets a specific, old bug class: use before set, reading a local variable before you’ve assigned it a value.
int scale(bool ready) {
int factor; // declared, never initialized
if (ready)
factor = compute();
return factor * 2; // if ready == false, factor was never set
}
To see what actually changed, you have to look at how a compiler optimizes. Optimization is the compiler rewriting your code to run faster while keeping the result the same, and to do that it has to make assumptions. The key assumption here is undefined behavior is treated as impossible. Before C++26, reading factor uninitialized was undefined behavior, so the compiler was entitled to assume that read never happens and rewrite the surrounding code on that basis.
The clearest example is an uninitialized bool:
bool b; // uninitialized if (b) foo(); if (!b) bar(); // UB: the compiler may legally call both, or neither
Because reading b is undefined behavior, the compiler doesn’t have to consider “what if it’s garbage.” It can assume b holds one consistent value and optimize the branches accordingly (nothing in the standard said it couldn’t). Mainstream compilers rarely do the most destructive version of this in practice, but that restraint was a convention, not a rule.
C++26 now makes it a rule. Reading an uninitialized local is now erroneous behavior (proposal P2795), a defined category that sits between “correct” and “anything goes”:
- The variable holds a fixed, specified value (implementations fill it with a set pattern), not whatever was left on the stack.
- Reading it is still wrong, and the compiler is encouraged to diagnose it. A hardened or sanitized build can trap on it.
- But it is bounded and reproducible. The compiler can no longer treat the read as impossible and rewrite around it. Same input, same result, every run.
It does not fix your logic: factor still isn’t the correct number. What changes is what that mistake is allowed to become.
If you actually want an uninitialized value (like a performance-critical buffer you fill yourself), you opt out per variable with [[indeterminate]], so the intent is visible in the code instead of hidden in an accident.
The payoff for a large codebase: recompiling as C++26 removes this undefined-behavior class everywhere at once, with no source changes. It won’t clear your bug tracker, but it turns a whole family of “used before it was set” hazards from nondeterministic landmines into deterministic, diagnosable bugs.
Contracts and the new async model, briefly
Two more features touch on safety without being the center of it.
Contracts arrived in C++26. You now get real language-level preconditions, postconditions, and a contract_assert statement, with four enforcement modes you can switch between at build time. (A precondition is a promise about what must be true when a function is called, and the runtime can check it for you.)
int scale(int value, int factor)
pre(factor > 0) // precondition: checked on entry
post(result: result >= value) // postcondition: checked on return
{
contract_assert(value >= 0); // checked mid-body
return value * factor;
}
Those four modes (ignore, observe, enforce, quick-enforce) let you decide at build time whether a failed check is skipped, logged, or trapped, so you can run enforced in testing and revert in release.
They are genuinely useful for encoding the assumptions your interfaces already depend on. Just be careful calling them a memory-safety feature. Their safety value was disputed inside the committee right up to the final vote, and a precondition is only as good as the one you remembered to write. Treat contracts as better defensive checks, not as guarantees.
std::execution (senders and receivers) gives C++ a built-in model for structured concurrency: a disciplined way to organize work that runs in parallel. It is designed to make data races much easier to avoid. (A data race is two threads touching the same memory at once, with at least one writing and no coordination between them, which is undefined behavior.)
It will not make concurrent code automatically race-free, since you can still share mutable state and get into trouble, but it gives you a framework built to steer around those mistakes. It is a significant addition. It is also heavy, it wants helper libraries today, and adopting it in an existing codebase is a multi-year project. Worth knowing about, but not part of your next-quarter safety plan.
What a large-codebase team should actually do
Here is the roadmap you can paste into a security questionnaire, in order of required effort.
Turn on the easy wins. Recompile as C++26 wherever your toolchain allows it, and you inherit erroneous behavior for uninitialized locals with zero code changes. Enable the hardened standard library in a staging build, measure the overhead against your real workload, and promote it to your production configuration if the cost is as low for you as it was for Google.
Harden the interfaces you already touch. As you work in a module, add contracts to the functions you are already modifying. It’s incremental by design. You are not stopping feature work to annotate the whole codebase; you are just reinforcing the parts that are already in your hands.
Write the roadmap the regulators actually asked for. The honest answer to the security questionnaire is not “we’re memory-safe now,” and it is not “nothing until C++29.” The better, more nuanced answer would be something along the lines of “We have enabled recompile-level hardening and the hardened standard library, we are adding contracts incrementally, and we are tracking profiles for the next standard.” That signals action, which is what the guidance was asking for.
There is a requirement nobody puts on the slide, though. You cannot harden what you cannot find. Enabling bounds checking is a flag; deciding where it matters means locating every risky access across the codebase. It is the operator[] on data that came from a file or a socket. Or the raw pointer arithmetic in the old subsystem nobody wants to touch. Or the unchecked index sitting one line away from a container you just hardened. On a million-line codebase, that audit is the actual work.
A practical adoption order: the free recompile-level wins first, hardened standard library next, contracts and profile-tracking as ongoing work.
Finding the risk before you can fix it
The audit is a code-comprehension problem before it is a safety problem. Before you can decide which of ten thousand indexing operations sit on an untrusted path, you have to find and detect them. Which means you need to find every reference to a symbol across the whole solution, jump to each call site, and understand the data flowing around it without losing an afternoon to the search.
Fast, accurate navigation is what turns a codebase-wide safety audit from a month into a week, and that is exactly the comprehension work that C++ Visual Studio plugins like Visual Assist are built for. It will not make your code memory-safe (no tool does that for you), but it makes the audit that has to happen first tractable.
The features that ship should be boring
The security team’s questionnaire does not have a good one-word answer, and that is fine. C++26’s real memory-safety story was never the profiles that made the headlines and then slipped a cycle. It is the unglamorous, recompile-level hardening that a real team can actually adopt without betting a release on it: uninitialized reads defanged, the standard library checkable, contracts where you want them, and a credible plan for the rest.
The rewrite fantasy is, for now, still a fantasy. Incremental hardening is here, and it’s an action step you can take this quarter. Sometimes the boring feature is the one that matters most.
.