Go inside Echobit.
A while ago, a friend of mine discovered an interesting discrepancy in how the Visual Studio debugger shows local variables in relation to for-loops. When he demonstrated the issue, I decided to investigate the problem a little further.
To start things off, consider the following code snippet:
void SomeFunction()
{
for (int i = 0; i < 1; ++i);
for (int i = 0; i < 1; ++i);
}
The function above contains two for-loops that do absolutely nothing useful (unless you consider looping useful). What makes these two for-statements interesting, though, is how they both use an iterator variable named i. According to the C standard (C98), i is local to the for-statement in which it is defined. That is, code outside the scope of the for-statement cannot access i.
Older Microsoft C++ compilers (pre-Visual Studio 2005) didn’t follow the standard and instead considered i to be valid outside the scope of the for-loop. Fortunately, Microsoft decided to make their newer versions of the compiler significantly more standards-compliant, and one of the things they fixed was the scope of for-loops. As a result, the two iterator variables (i) in the code snippet above are therefore considered to be separate entities.
However, it looks like they forgot to update the Visual Studio debugger to reflect this change. The screen shot of the Locals window below illustrates what I’m talking about:
For some reason, the variable i is shown twice in the list when execution hits the second loop, even though it has gone out of scope in the first loop and is no longer valid. The disassembly of the code reveals that i has been created as a local variable on the stack (a separate stack location exists for each loop variable). The compiler could easily have used the same local stack location for both of these loop variables, of course, since they’re never used at the same time, but for this post we’re only considering unoptimized debug builds.
The Visual Studio debugger and WinDbg differ somewhat in how they show these stale variables. The first simply shows all the variables in the Locals window as in the previous screenshot, which makes it really hard to distinguish them from each other. WinDbg, on the other hand, shows the variables that have gone out of scope as <Eclipsed>:
Executing “dv i” in WinDbg yields the following output:
0:000> dv i
i = 1
i = 1
This tells us that the debugger is very much aware of the presence of both of the iterator variables even though neither is valid anymore.
If we dig further, it turns out that variables declared in the body of a single-line for-loop also show up in the debugger once they have gone out of scope. The following piece of code illustrates that:
for (int i = 0; i < 1; ++i)
int foo = 0;
for (int i = 0; i < 1; ++i)
int foo = 0;
int i = 1;
// When we get here, we have three i's and two foo's in the Locals window
Contrast this to the behavior of e.g. while-loops where variables declared in the body of the loop are correctly removed from the Locals window once they go out of scope.
Interestingly, the issue seems to only manifest itself whenever scope brackets, { and }, aren’t used. Consider the following example:
for (int i = 0; i < 1; ++i)
{
int foo = 0;
}
for (int i = 0; i < 1; ++i)
{
int foo = 0;
}
{
int bar = 1;
}
// When we get here, only the two i's are present in the Locals window
In this example, there is always only one foo or one bar shown in the debugger, and they disappear from the Locals window whenever the variable goes out of scope. As soon as the scope brackets are removed, though, we get the aforementioned behavior.
It’s worth mentioning that the assembly code of a for-loop with and without the scope brackets is, of course, completely identical.
This lingering local variable issue is most likely a bug in the debugger since the for-loops should essentially work just like while- and do-while-loops. It’s probably a relic from the days where the Visual C++ compiler didn’t conform as much to the C++ standard as it does now and variables declared in for-loops didn’t just have local scope.
Implications
At first, this behavior seems quite harmless. However, take a look at what happens in the following case:
int n = /* someArbitraryNumber */; for (int i = 0; i < n; ++i); int m = /* someOtherArbitraryNumber */; for (int i = 0; i < m; ++i); // .. A few more loops using i .. int i = /* Complex calculation */; ... // When looking at i in the Locals window at this point, it's difficult to determine exactly // which one is used in the calculation below since multiple exist. int result = i + /* some other variables */;
In the example above, the variable i is used multiple times. When you get to calculating the result (the last line), how do you know which i to look at in the debugger? If you’re lucky enough to step over the complex calculation in the debugger, the correct variable will most likely be highlighted in red by Visual Studio, but you could just as well have set a breakpoint further down the code path or have broken into the debugger due to an exception.
It’s fairly easy to get past this issue simply by looking at the actual instruction in disassembly mode to figure out the stack location that’s being referred to, or by hovering the mouse over the variable in the source file, but it seems to me that this is unnecessary work for something that the debugger should be able to tell you right away in the Locals window. After all, the previous instances of i have gone out of scope and are no longer valid, so why show them?
Test Script
I’ve uploaded a simple test script if you want to test out the behavior yourself: loop.cpp.


No comments, yet.