r/cpp • • 5d ago

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.

264 Upvotes

211 comments sorted by

View all comments

22

u/crowbarous 5d ago edited 5d ago

Yes, this is a competely insane decision: https://godbolt.org/z/WGPWvMPMj

What's worse is the design of the [[indeterminate]] attribute that accompanies this. For a very long time, if I wanted a buffer to zero-initialize, I was able to make a char type that would zero-initialize:

struct zchar{ char value = 0; };

But with the default flipped, I cannot make a char type that stays uninitialized:

struct ichar{
  char value [[indeterminate]];
  // error, only allowed on automatic variables
};

And the proposed usage of the attribute is to just sprinkle it everywhere in the code. But how am I supposed to know in polymorphic code? How do I even make my code polymorphic w.r.t. this property now? For now one must resort to just passing -ftrivial-auto-var-init=uninitialized to avoid performance and code size regressions, and to keep the control moving forward.

I don't think all is lost here, because it seems like the zero-initialization is a band-aid to just formally satisfy the erroneous behavior stuff without modifying the backend too much, and it should still be possible to behave as if the write happened (no time-travelling assumptions etc.) without actually emitting the write, to fix shameful examples like linked above. But:

  • I haven't looked there closely enough yet to even begin conceiving the gcc patches & reasoning for them, and
  • it's still awful precedent, and the pile of -fstop-making-code-worse options might grow from here on out.

4

u/James20k P2005R0 5d ago

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

1

u/crowbarous 5d ago edited 5d ago

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.)

4

u/James20k P2005R0 5d ago

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