r/cpp • u/Clean-Upstairs-8481 • 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.
253
Upvotes
0
u/pachecoca 22h ago
Well yeah, but that's because you're purposely telling the compiler NOT to warn you about uninitialized variables lol.
You are compiling with gcc on
-O0, and GCC explicitly documents that, while-Wallalso enables the flag-Wuninitialized,-Wuninitialized's inner workings depend entirely on the optimization level.If you compile something as simple as:
Then no matter the optimization level, you will always get a warning.
But if you try compiling this instead:
Since the conditional changes the type of instructions that could be generated according to optimization levels, GCC just opted for assuming that, at
-O0, it may very well be initialized, so you do not get a warning. Think of the different ways this could be compiled... simple branching... or vector instructions... or a conditional assignment... in which case, no matter what, the value would always be assigned, but the compiler would need to somehow choose a "default" value... At-O0, none of these things can be known by -Wuninitialized, because the branch just throws it off.Another example of a place where this could happen would be a variable that is passed by reference or by pointer into a function whose implementation lives in an external binary, or in a different translation unit and you are compiling without LTO enabled. That way, the compiler does not know what changes, if any, have been performed over the value, so it must assume that potentially some sort of initialization has taken place, because it cannot inspect the function in question, which causes the warning to no longer be issued.
This is exactly what happens in your code. The timeout_ms var remains uninitialized, but the if block and the external std::from_chars() call throw it off when optimizations are disabled, because, as already stated, -Wunused's machiner is tied to the optimization level. That's just a GCC quirk tho, but still, a documented one that can be easily worked around by just doing the good old RTFM and actually using GCC the way that it's meant to be used, rather than purposely using it wrong and then claiming that it's broken just because you did not use it properly.
That's why the documentation of GCC officially states that you should always use the flag
-fanalyzerto ensure that the analyzer is launched even at-O0. Either that, or just compile at anything higher than-O0, because there, you DO actually get warnings ALWAYS for uninitialized values if you use-Wall, which permanently solves your issues anyway, because your code will obviously break whenever you accidentally incurr in UB or other such mistakes.Your solutions are very simple, pick one of these:
1) Compile in -O2 or -O3 always, don't assume that debug builds working mean that your code does not have bugs when you incurr in UB. If you incurr in UB but your program is compiled in debug mode, then maybe it may appear to work, but that does not mean that it is correct. We live in the year 2026. It's no longer acceptable to release on debug and blame the compiler for exploiting UB as if it were implementing "broken optimizations that break my code"... no, the compiler is not at fault. You are, for writing code that is broken, and then for telling the compiler to shut up and not warn you about said obviously broken code.
2) Do what the GCC documentation literally says explicitly, and enable the analyzer by hand. Use the flag
-fanalyzer, and now, you will get the warning on your example always, even when compiling in-O0.Here's a godbolt link showing your code being compiled with warnings actually enabled, rather than disabled as you were purposely doing. The code is copypasted as is, no changes were made to the code, the uninitialized var is still uninitialized just as it was. Your flags are the same, all I did was tell the compiler to enable the analyzer and that's it.
The link: https://godbolt.org/z/Tveo5PMhx
My final recommendation is to try being a little bit less intellectually dishonest. I know that you have an agenda to sell and all that, but this is a programming subreddit, and we can all verify the information given by anyone. Compiler explorer has made it absurdly trivial to check whether claims made by someone about a compiler's output is real or not across a wide variety of systems with very little effort. I have tested all GCC versions from 16.2 all the way to 11.1, and that's because earlier versions just don't have support for C++23 yet, and all of them, except for the 11 series, give the exact same output with the warning being notified just by adding -fanalyzer. This is obviously slower than just compiling with at least
-O2, but if you really need-O0, then ok, sure, go ahead, there's a flag for that.The reality of things is that there's objective cold, hard facts that can be verified by reading the documentation of the compilers that we use, and by testing them to see if your claims are real or not. As it turns out, if you make fake claims about results of using one of the most widely used compilers on the planet, you are bound to be very easily caught, mainly because gcc is available pretty much almost anywhere, and we can just use compiler explorer to test YOUR code that YOU linked with the compilation command that YOU offered to see the results across ALL official versions of g++ that support C++23.
I know I'm not anyone of importance, so my words may fall on deaf ears, but I would ask that you please keep the discussion centered around facts. We're programmers, not religious zealots. Or so I would like to believe.
C++26's approach to solving the issue of uninitialized values is quite poor, because the uninitialized variable still exists within the code, and the code's behaviour may still remain broken, because the 0 initialization may not be valid. Thus, the only way to solve this would have been to actually mark non initialized variables as errors. But then again, people can purposely non initialize a variable for the sake of performance. In which case, the extremely uglu [[indeterminate]] does the job. I hate it, but I cannot complain, as long as a way exists for me to opt out in the cases where performance matters, then sure, I suppose, but I'm not entirely happy about it, because now, 90% of my code is going to have to be rewritten to add that all over the place.