If you are a C++ programmer, you know that declaring a global variable without initialising it is safe because it is placed in the BSS section and the compiler makes sure it is zero-filled. However, the same is not true for stack variables, or local or automatic variables. If you haven’t initialised your local variables, they are not automatically initialised by the compiler, as there is no BSS section in the stack.

C++23 local variable

Let’s have a look at the following sample code:

Now, let’s compile the program with optimisation level set to O0:

At optimisation level O0, we can verify the uninitialised state of the local variable timeout_ms declared in the function read_timeout_ms(). Let’s run it with Valgrind now:

Zoom in on the following lines:

It is complaining about the variable timeout_ms in the function read_timeout_ms(). Now, let’s take a look at what happens if we simply compile the above program at optimisation level O0 with a C++26 compiler and run Valgrind on the executable.

C++26 local variable

Compile the above code with C++26 as follows:

I have explained the C++26 compiler setup in one of the previous posts GCC 16 setup guide.

Then run the executable timeout_cxx26 with Valgrind:

Notice that Valgrind is no longer complaining about the uninitialised variable timeout_ms in the function read_timeout_ms(). The reason is that GCC 16 zero-fills local variables when compiling in C++26 mode.

Optimisation level O2

The behaviour is slightly different if we set the optimisation level to O2. At optimisation level O2, Valgrind is not quite suitable to see the difference because the compiler starts using registers to move around the variables and their initialisation. But we can see the difference from the assembly. Generate the assembly code out of the source code for each compiler as follows:

C++26

This generates the assembly code with the help of the C++26 compiler, optimisation set at O2 level, and the -fverbose-asm switch adds comments showing which source line each instruction comes from. Now, if you load the assembly code in an editor, you should be able to locate the assembly code below:

This is the part of the assembly generated for the read_timeout_ms() function, and the line below is making sure that the variable timeout_ms is initialised to zero:

It is an XOR operation, and simply put, as both the operands are ebx, the output of the operation will be zero.

C++23

You can generate the same assembly code using C++23 as well, but the same zero initialisation will not be found in the case of C++23. Do the following:

If you examine the assembly code generated for the function read_timeout_ms(), you won’t be able to see the same initialisation code for the timeout_ms variable.

So there you go, it is now proven, and hence all the more reason for you to try it yourself.

The sample source code and the generated assembly code can be found in the below provided gitrepository.

Source code cpp_26_uninitialised The complete example from this post, ready to build with GCC 16. View on GitHub


Discover more from Tech For Talk

Subscribe to get the latest posts sent to your email.

Leave a Reply