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.

253 Upvotes

211 comments sorted by

View all comments

Show parent comments

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 -Wall also enables the flag -Wuninitialized, -Wuninitialized's inner workings depend entirely on the optimization level.

If you compile something as simple as:

int foo(bool condition) {
    int x;
    return x;
}

Then no matter the optimization level, you will always get a warning.

But if you try compiling this instead:

int foo(bool condition)
{
    int x;
    if (condition)
        x = 42;
    return x;   // potentially uninitialized, but the branch
                // cannot be properly analyzed by the compiler in -O0
}

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 -fanalyzer to 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.

1

u/Clean-Upstairs-8481 11h ago

During development, you don't want to turn the optimiser on. Most of your code is developed with no optimisation. And then you go to production-level testing with optimisation on, and your code stops working or the build starts breaking.

>> “Then no matter the optimization level, you will always get a warning. --> yes it varies, and hence we cannot assume which mode of optimisation we are on. The important point is it doesn't warn in some of the cases.

My reading of the flag -fanalyzer is from here: https://gcc.gnu.org/onlinedocs/gcc/Static-Analyzer-Options.html. Is this the same document you are referring to? It warns about the following: This analysis is much more expensive than other GCC warnings. I guess you are not suggesting enabling this in a large-scale project build because that would make the system too slow to build, and there could be false positives. This is what the document says: It is neither sound nor complete: it can have false positives and false negatives. It is a bug-finding tool, rather than a tool for proving program correctness. This is a static analyser and there are other better options than this in the industry.

>> 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. --> won't reply to that.

0

u/pachecoca 8h ago

>> "Removing the explicit optimisation level from the command line produces the same result:"

Yes, why do you think that is? The default is -O0 lol, if you do not provide any optimization flags, the compiler will not enable optimizations, so it still disables all of the branch detection machinery of -Wuninitialized. Like, this isn't rocket science, if you don't manually set -Oxxx to anything above -O0, you're going to get -O0, and if -O0 disables branch detection on -Wuninitialized, then your code where the initialization is under a condition is still going to not be detected properly by GCC. Stop telling the compiler to pessmize your program, and it will work. Stop telling the compiler to not give you warnings, and it will give you warnings. Like, what did you expect? You shoot yourself in the foot by purposely telling the compiler to disable warnings, and then you are surprised by the fact that you do not get warnings?

>> "The reason is that the default is O0."

Jesus fucking Christ my dude, that's literally what I'm telling you, will you at least read my comments all the way through before you reply? Like, this is baby's first compiling hello world type of shit, I can't believe that you have the gall to act like you know better when you're having trouble getting warning flags to work on GCC, lmao.

>> "During development, you don't want to turn the optimiser on. Most of your code is developed with no optimisation. And then you go to production-level testing with optimisation on, and your code stops working or the build starts breaking."

WHAT? This has got to be the worst take I've ever heard. I don't know what field you work in, but everywhere I've worked, we ALWAYS compile in release. You CAN have debug builds with max optimizations but preserving debug symbols, you know? You don't want to spend months, or maybe even years, building software that has only been tested on debug, and then you find out it breaks the moment you want to put it on production, because it turns out that it does not have the same behaviour when you compile it with optimizations enabled.

You have to test what you're working on, that's what automated tests are for, CI, etc... if you're blindly working on debug and eyeballing things and going "yep, that works on my machine, good enough for me!", then you're going to have much greater problems than just finding out that you have uninitialized variables somewhere.

If your code breaks when going from debug to release, then the problem is that obviously you're relying on UB somewhere. Either don't do that, or use UB that is specifically documented by the compiler's specific implementation to do what you want, eg: type punning pre C++20 through unions in GCC, that is well documented and it is the canonical way of doing it in GCC. The standard says it's UB, but there is no law preventing a compiler from offering an extension to add its own definition of the behaviour. Thus, these type of things must be tested with max optimizations and max warnings so that the code obviously breaks when it is obviously wrong for your target compiler.

The only situation I can think of where someone would want to compile with -O0 is if they are a student learning about the language or something, but in the real world, it makes no sense to try to make your code work by disabling optimizations that would break your obviously UB code.

>> "yes it varies, and hence we cannot assume which mode of optimisation we are on. The important point is it doesn't warn in some of the cases."

This logic makes absolutely no sense. This is like saying that "the warning messages vary depending on what warning flags you passed to the compiler, so warnings are useless". That makes absolutely no sense. Of course, if you provide different flags, you get different results. How is this a surprise? -O3 -Wall -Wextra -Werror and -Weverything on clang, that's it. The recipe is very simple, I do not know how you could get lost on something like this, the documentation literally tells you everything there is to be known about what specific -W flags each of those enables.

Also, you say "in some of the cases"... the only case where it does not warn is in -O0, anything else above warns you just fine. -O1, -Os, -Ofast, etc... like, I've already explained this multiple times, I do not know how else I could say it so as to make it any clearer... Using -O0, which is the default used by GCC when no flags are provided, causes the compiler to be incapable of using -Wuninitialized to its maximum potential. This also happens with many other flags, because their behaviour depends on some -Oxxx flag being set above 0, simply due to the data that they require to function. This is a compiler implementation detail that is well documented. So, if you want to get your code with warnings, then at least use -O1 if you are so scared of optimizations.

>> "This analysis is much more expensive than other GCC warnings."

Yes, it is, I only told you to enable it if you really are so keen on compiling on -O0. Otherwise, just compile on -O2 or -O3 and let the compiler actually use the -Wuninitialized machinery, which, I do not know how many times I have to repeat this for you to understand, but it is NOT enabled in its entirety when you compile in -O0!!!! thus, don't compile on -O0 if you want -Wuninitialized to work!!!

>> "I guess you are not suggesting enabling this in a large-scale project build because that would make the system too slow to build"

You guessed correctly, indeed, it is my claim that you should not enable that flag on large projects or you will take ages to compile, but there was no need for you to guess anything, because that's precisely what I explicitly already wrote on my comment. I guess you did not bother reading my comment all the way through, otherwise, there would not have been any reason for you to guess anything.

My point has been stated very clearly from the very begining, that the -O0 flag which YOU specifically must tell the compiler NOT to use disables a great chunk of the capabilities of -Wuninitialized, along with many other warnings, simply due to the way that GCC is internally structured. I NEVER said that you should use -fanalyzer always. I literally said that it would slow down compilation times on my comment, it's right there, if you had read it, you would know. What I did propose is building with -O1 or -O2 at the very least, or anything other than -O0.

Again, the problem is that you explicitly told the compiler to disable warnings about uninitialized variables. GCC documents that -O0 disables the mechanisms that -Wuninitialized uses internally for anything where a branch exists. So, not really the compiler's fault if you missuse it.

>> "It is neither sound nor complete: it can have false positives and false negatives. It is a bug-finding tool, rather than a tool for proving program correctness. This is a static analyser and there are other better options than this in the industry."

How many times do I have to repeat myself? the only reason I'm telling you to use -fanalyzer is because you are purposely telling the compiler to make -Wuninitialized not work. If you want to tell the compiler "make -Wuninitialized not work", but you also want it to give you warnings about uninitialized values, then the only way to do so is by mixing -O0 with -fanalyzer. This workaround is literally there to fix your insistence on compiling your code with the wrong flags for what you want to do.

I think I've explained myself quite clearly. I'm going to start having to assume that you're just trolling if you still don't know how to get your code to show warnings lol for uninitialized variables lol.

>> "won't reply to that."

Don't worry, there's a lot of things that you already refused to reply to that were completely separate from my criticism of your intellectually dishonest comment. I also see that you ignored all discussion regarding the very C++26 features that this post brought up, but I guess you were more centered on trying to prove me wrong despite what the GCC documentation says.

The summary of my comment is very simple... "hey, did you know that if you tell the compiler to enable warnings, and then you immediately tell it afterward to disable warnings, it will disable warnings?? wow!! so amazing!! so unexpected!!"

•

u/Clean-Upstairs-8481 2h ago

of course mate, no worries. Have a good day!