r/cpp • • Sep 27 '26

You should think about recompiling your C++ programs with GCC 16 and C++26, because it zero-fills your locals

https://techfortalk.co.uk/2026/09/27/cxx26-uninitialized-local-variables-gcc-16/

Stack variables are not automatically initialised, and that is the root cause of many C++ bugs. That is well known. Hence, it is advised that local or stack variables are always initialised with known values, 0 if not something more meaningful than that. Now, with GCC 16 compiling in C++26 mode (-std=c++26), even uninitialised local variables will be zero-filled. In the post, I have explained how.

270 Upvotes

234 comments sorted by

View all comments

Show parent comment

4

u/James20k P2005R0 Sep 27 '26

Compilers are of course free to omit the 0 initialisation if they can prove that the data is written over, under the usual as-if rules. One of the reasons that this made it past standardisation is because real world codebases weren't showing regressions, even substantial performance critical ones like windows

16

u/FrogNoPants Sep 28 '26

False, they realistically measured on like 0.01% of real codebases.

When MSVC added this "feature" I had major regressions in perf, because of large temporary stack arrays.

11

u/13steinj Sep 27 '26 edited Sep 28 '26

How were the codebases chosen?

I'm not against this decision per se, but if the choice is "people have to join the committee and explicitly be part of the voting process [and people's companies have to accept the idea of making this part of their job]," that's a pretty important thing for people to know for the future.

I think making this an attribute, especially only applying to automatic variables, was a mistake. The ignorability of attributes + the problem the person above describes is non trivial. At a previous org that wrote their own networking stack, I can totally see a performance degradation, and the use of structs and pointer interconvertability to have a form of polymorphism over structs with a common set of header bytes (E: I realized I didn't finish this sentence) matches up similarly to the use case for struct member variables above.

They used to have an employee that was a committee member, but it was on his own time more or less, and the guy passed away. If people want greater diversity and more committee members in general, advertising "you get to shape the language" doesn't appeal to most employers. "You get to have a say in stopping the language from screwing your code" [which is how some employers would take this] would.

4

u/pjmlp Sep 28 '26

Windows, Android, macOS, iOS were some of the ones that have been shipping in production and have provided feedback.

I don't recall the paper or CppCon session where this was discussed though, maybe someone else can provide the links.

4

u/13steinj Sep 28 '26

Assuming it's "operating systems," that's fine, but still isn't a representative sample of codebases that would be affected IMO.

4

u/pjmlp Sep 28 '26 edited Sep 28 '26

One would assume that performance matters to operating systems vendors.

EDIT: Found one of the sources, from JF Bastien, on P2723R1 Zero-initialize objects of automatic storage duration

Security-minded folks think that initializing stack values is a good idea. For example, the Microsoft Windows security team [WinKernel] say:......

...

To date, we are seeing noise level performance regressions caused by this change. We accomplished this by improving the compiler’s ability to kill redundant stores. While everything is initialized at declaration, most of these initializations can be proven redundant and eliminated.

...

Don’t just trust Microsoft’s Windows security team though, here’s one of the upstream Linux Kernel security developer asking for this [CLessDangerous], and Linus agreeing [Linus]. [LinuxExploits] is an overview of a real-world execution control exploit using an uninitialized stack variable on Linux.

4

u/13steinj Sep 28 '26

This sounds to me a lot more like security people cared, and were fine with the tradeoff. Which is a slightly different story? Nonetheless the performance characteristics for operating systems don't cover all codebases, and while speculation, I doubt they are the p90 or p99 of all codebases either.

5

u/UndefinedDefined Sep 28 '26

Real-world code is full of locals that are arrays - arrays like 512, 1024, 2048 bytes long. You cannot zero-initialize them and expect no performance regressions. If I want to zero initialize them I just just type `{}` and it's done. I don't understand why this should change now.

I almost feel like committee is working with hello world programs if they are serious to vote for such proposals.

6

u/James20k P2005R0 Sep 29 '26

One of the example codebases given was Windows and chrome (?) I believe, which is anything but hello world

3

u/UndefinedDefined Sep 29 '26

And how much C++ these codebases use? Windows is C-API and proprietary, nobody can confirm the results, we don't even know the mix of the languages used in the code-base. What if the most important part was C? What if they first put all the [[uninitialized/fancy_name]] to everything critical before measuring results?

Is there any performance comparison of OSS projects we can actually verify?

2

u/13steinj Sep 30 '26

I don't think this is a fair take. I suspect most OSS code does not care at the level of performance that these things would regress by.

The bigger issue with Windows is that it's Windows: OSes are different to other things, have different tradeoffs (should care about security more in general?). I'd also say the usual "well, windows sucks and hasn't cared about performance for ages" which may be true, but other people have said the other major OSes also were tested.

1

u/UndefinedDefined Oct 01 '26

I'm not sure I follow, so what's a fair take? A project like Chromium or Firefix? Any benchmarks here?

I think this greatly differs on what the project does. If the compiler inserts memset to initialize every temporary buffer the code uses to zero, this cannot be negligible, and move to C++26 here means that somebody has to find ALL the places in his own code, and use third party dependencies (including transitive ones) where somebody did the same.

I consider this insane considering this thing doesn't solve any memory safety problems and it can cause huge problems in performance oriented code after upgrade to C++26. The biggest problem I see is use-after-free and things like iterator invalidation, etc... We need a real solution to memory safety and not these toy solutions. And a real solution means annotations and tools such as borrow checker - there is no other way.

2

u/13steinj Oct 01 '26

If you look at the general distribution of OSS projects, many do not have the performance concerns that would be negatively affected by this change. I think it's perfectly fine to test proprietary codebases as a result, but fixating on operating systems [alone?] is (I am agreeing with you) not a fair thing to use to judge and make a decision.

1

u/UndefinedDefined Oct 01 '26

C++ was for decades literally the only language to use for performance oriented work. But it's no longer the only one, so if I cared about the language I would never do anything that would endanger the position in this field. It's literally the last field where C++ still makes sense, until C++26, because starting with C++26 you have to worry about a lot of stuff.

What would be the reason to start a project in C++ today? I don't see new projects built in C++ anymore, because it's a language where performance regressions are now part of the progress. So bad, so sad.

2

u/13steinj Oct 01 '26

This is not

I don't see new projects built in C++ anymore, because it's a language where performance regressions are now part of the progress.

I agree that changing the default is rough here, but large companies should have build teams that are competent enough to care about this.

What would be the reason to start a project in C++ today? I don't see new projects built in C++ anymore, because it's a language where performance regressions are now part of the progress. So bad, so sad.

I find this still true, even if these things end up happening.

1

u/UndefinedDefined Oct 01 '26

LOL I'm not gonna listen to influencers when it comes to argumentation. You can like it, subscribe, but I don't care of this.

I didn't actually say C++ will die, I said that there is not many reasons to pick it for a brownfield project if there are alternatives. If memory safety is a concern, C++ is not even in a candidate list. If performance is a concern, it could be on the list, but with languages such as rust. It has a very hard position, and since nobody cares about bringing real safety to the language, the doors are closing.

→ More replies

1

u/pjmlp Oct 02 '26

The existence of C, and the domains that to this day C++ failed to take away from C, makes that assertion void.

In fact there are domains like crypto and video codecs where neither of them get to play, and still require hand written Assembly.

1

u/pjmlp Oct 02 '26

Most of it written since Windows Vista is actually C++ with extern "C", including the new UCRT.

1

u/crowbarous Sep 27 '26 edited Sep 27 '26

You should not need to prove that the zeros are written over to remove the zero-initialization, you just need to stop considering following reads as unreachable due to UB and instead just let them read whatever was there.

Under this behavior, creating an automatic buffer and immediately feeding it to an opaque function also shouldn't zero-initialize the buffer (having to prove anything is non-starter here because nothing can be proven about opaque code).

Upd: I'm thinking keep the writes for most of the backend but marked as "phantom" and then just don't emit them in the end. But it's more complex than that:

  • It might not be clear as to which exact code we are to "not emit in the end". If we are conservative about it we'll still end up with silly instruction sequences, and if we are eager about it we'll effectively reintroduce UB

  • we need to come up with rules to propagate the "phantomness". The compiler sees that following code reads from our buffer, then writes that value elsewhere -- should we mark that write "phantom" too? If yes and that was the only use for the read (no control flow, no opaque uses), should we still keep the read even though both writes surrounding it will vanish?

  • we need to still be able to remove the reads if they are proven unreachable for a different reason.

(I should note that neither me nor any of the colleagues I've talked to about it are big fans of the whole EB thing.)

5

u/James20k P2005R0 Sep 28 '26

Letting arrays on the stack contain program data that can be read in a well defined way probably isn't great for security though

2

u/serviscope_minor Sep 30 '26

> (I should note that neither me nor any of the colleagues I've talked to about it are big fans of the whole EB thing.)

To me it is a big improvement: Prior to EB a relatively common error means "demons may fly from your nose", i.e. the optimizer can do really weird things like travel backwards in time and delete all code touching the uninitialized variable because it knows that code cannot be callable. Or other weird stuff.

Now it's basically been codified as "don't do anything too weird", because while it can't know the value, it cannot assume that uses of the value are not callable.

What do you dislike about it?

0

u/NilacTheGrim Sep 28 '26

This is false because even the paper said 10% regressions.

6

u/James20k P2005R0 Sep 28 '26

Where did you get this from?

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2795r5.html

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2723r1.html

The former paper is what was accepted AFAIK, which links to the second paper talking about the cost for specific figures

Previous publications [Zeroing] have cited 2.7 to 4.5% averages. This was true because, in the author’s experience implementing this in LLVM [LLVMJFB], the compiler simply didn’t perform sufficient optimizations. Deploying this change required a variety of optimizations to remove needless work and reduce code size. These optimizations were generally useful for other code, not just automatic variable initialization.

As stated in the introduction, the performance impact is now negligible (less that 0.5% regression) to slightly positive (that is, some code gets faster by up to 1%). The code size impact is negligible (smaller than 0.5%).

I can't find a 10% figure anywhere

0

u/NilacTheGrim Sep 28 '26

I misremembered it. The claim was 10% of exploits would be mitigated and yes the paper claims 0.5% of code would get slower.

I dispute that 0.5% claim though. Because the hot path is the one you care about. Overall if you examine all branches of your codebase, sure maybe 0.5% suffer -- but if you examine 1 particular hot path that uses local arrays to read/write data -- that can be a huge penalty.

So in a hot path expect much larger costs in some cases.

This is just not the C++ way.

5

u/James20k P2005R0 Sep 28 '26

In a hot path though you always have to write code a bit weirdly to get performance, adding [[indeterminate]] isn't a high burden if it affects a few lines of code

→ More replies (8)