Shadowed Variables in the Debugger

I had an interesting run-in with a C++ debugger recently. Using Visual Studio Code and GDB, I was debugging code similar to the following:

    int a = 0;
    int b = 5;

    while(a < b) {
            a = b;
            printf("a = %d\n", a);

            //more code in the loop
    }   

What was strange is that when I had set it to stop on the statement a = b; and checked the value of b at that point, the debugger said that b = 32767, which was wrong. I stepped one line forward and checked the value of a. That showed a = 5 and b = 32767, but I had just set them equal to each other! Then I checked the result of the printf and that showed up as a = 5 as well. What was going on?

My first guess was that it was a GDB bug. I had seen some questions on StackOverflow (like this one: https://stackoverflow.com/questions/14734078/gdb-prints-wrong-values-when-modifying-arguments) saying that if GDB was giving incorrect variable values, that you need to compile with -fvar-tracking. After trying this and getting no results, I figured maybe I should just try a different debugger.

LLDB looked promising, so I gave that a shot. The same thing occurred! Then I thought it was a Visual Studio Code issue, so I tried debugging just from the terminal. That was no better either. Getting desperate, I thought it might be a Linux issue of some kind, so I tried compiling it with Visual Studio proper on Windows, thinking that surely that must work. And I was wrong again.

At this point, I looked over the code carefully and could not see what could be the problem. It looked so straightforward that I had no idea what could be going wrong.

Finally, I decided I might need to post it as a StackOverflow question and I might need to file a couple of bug reports, so I started seeing if I could cut it down to as small a piece of code as possible. That was when I finally found the error. Looking back, I should have tried this approach first.

When I commented out the extra code in the loop, the problem completely disappeared. I failed to see how this could be possible, since C++ is a procedural language, and those instructions have not been executed in any way. Turns out, later in the loop, I had accidentally redefined b.

    int a = 0;
    int b = 5;

    while(a < b) {
            a = b;
            printf("a = %d\n", a);

            //later in the loop
            int b = b + 1;//accidentally put the type in.
    }   

This caused the statement a = b to execute properly, and because the second definition of b was not yet defined, it used the non-locally scoped one. The debugger, on the other hand, does not take that into account and simply uses the in-scope, but un-initialized, one. This code has caused the mismatch between the compiler and debugger in the latest versions of GDB, LLDB, and the Visual Studio debugger (as of February 2020).


    #include <stdio.h>
    
    int main() {
            int a = 0;
            int b = 1;
            {
                    a = b;
                    //^The b in this statement equals 1, but the debugger reports it
                    //as some garbage value.
                    int b = 0;
                    printf("a = %d, b = %d\n", a, b);
            }
            return 0;
    }

Now, this is a pretty rare bug to run into, but neither the compiler or debugger was much help in catching it. For example, I compiled the above code with g++, Clang, and MSVC, and these are the results:

Flags Warnings
(None) (None)
-Wall (None)
-Wextra (None)
-Weverything declaration shadows a local variable
Flags Warnings
(None) (None)
-Wall (None)
-Wextra (None)
-Wshadow declaration of ‘b’ shadows a previous local
Flags Warnings
(None) (None)
/W4 declaration of 'b' hides previous local declaration
-Wall declaration of 'b' hides previous local declaration

There are no cases that come to mind where this would both be intentional and in good style, and I would think it should at least generate a warning, if not an error. None of the compilers generate anything specific to this situation, and unless the warning level is turned up fairly high, it doesn’t even alert you to the variable hiding.