5 Awesome Visual Studio Debugger FeaturesBy Alex Allain Here, you'll learn about some of Visual Studio's more advanced debugger features, such as stepping out of a function, taking advantage of the autos window, using run to the cursor to set temporary breakpoints, modifying a variable's value to test potential fixes, and using Set Next Statement to turn back the clock and re-execute code. Sample (Buggy) CodeEach of the following examples use this code, which contains several bugs:#include <vector> #include <iostream> class Data { public: Data () { _values.push_back( 1 ); _values.push_back( 2 ); } int getSum() { int total; for ( std::vector<int>::iterator itr = _values.begin(), end = _values.end(); itr != end; ++itr ) { total += *itr; } return total; } int getCount() { return _values.size(); } private: std::vector<int> _values; }; double compute_average( int sum, int count ) { return sum / count; } int main() { Data d; std::cout << compute_average( d.getSum(), d.getCount() ) << std::endl; } 1. Step Out of the Current FunctionSo you've written a function call, like so:int main() { Data d; std::cout << compute_average( d.getSum(), d.getCount() ) << std::endl; }And the compute_average function is not returning the correct value. If you were to use a debugger, and you wanted to step into compute_average, you could, of course, put a breakpoint inside compute_average, but what if it were called from several places? Visual Studio has a very convenient feature of its debugger, that will allow you to step into compute_average very quickly. Set a breakpoint on that line: Once the debugger hits it, then step into the function. getSum and getCount will both be called before compute_average. Normally, you'd keep hitting next to get out of getSum and getCount, but with Visual Studio, you can quickly hit Shift-F11 to step out of the current function and return to the next call. Once you step out of the current function, you're taken back to the original breakpoint. But now you can step in again, to go to the next function. Repeat until you've drilled down into the function you want. When to Use This TrickObviously, there are times when it makes more sense to just set a breakpoint in your target function. But when doing so would require ignoring hundreds of calls to that function to find the one you want, it might not be worth it. Using step out provides a quick way of getting into a function without having to find the function and set a temporary break point (or use run to cursor).2. Use the Auto Window to See Result of FunctionsOne of the most frustrating parts of debugging is when you have a function call whose result isn't stored anywhere, and you really want to know what it returned.Sometimes, programmers write code like this, just to work around the issue: int computed_avg; computed_avg = compute_average( d.getSum(), d.getCount() ); std::cout << computed_avg << std::endl;(Obviously, in this case, the program prints out the value, so all is not lost. This is rarely true in the general case.) Fortunately, the autos window has good courtesy to display the result of a function evaluation: When to Use this TrickWhenever you want to see a return value! Note that you the autos window will eventually erase the return value as you execute code, so be sure to check your return value immediately.Did you know...Most return values are also stored in the EAX register--you can look in EAX to find the return value, if you need it.3. Run to CursorRun to cursor is a great way of avoiding the step-step-step-step-step-oh-no-I-stepped-over-it problem, where you know where you want to be, getting to it requires stepping multiple times, and you get impatient.In effect, run-to-cursor is just a shortcut for setting a breakpoint that is immediately cleared once it's hit; nevertheless, it's a very convenient feature. It is most useful when you have a piece of code that is called frequently from different places, and you only care about one code path. Then you can place a breakpoint at the start of that path and use run-to-cursor to get to the point you really care about. Going back to our sample program, we can use run-to-cursor rather than the step-out trick, to get into compute_average. (Of course, we could just put a breakpoint in compute_average; for the purpose of making this example sensible, please imagine 15-20 calls to compute_average that all work correctly, taking place before the broken call.) When to Use this TrickAny time you want a throwaway breakpoint or want to avoid single-stepping through a lot of code. Be careful, though, that you run to a part of the code that will actually be executed. Watch out for early returns from a function within a loop, for instance.4. Modify Any VariableNow that we're in the compute_average function, and we know that the value being returned is waayyy too big, we can check the arguments to the function. It turns out that sum is very large. We'll deal with that in a bit. First, let's test and make sure that the function works if we do get the right value.Obviously, one way of doing this would be to pass in a new value. But Visual Studio conveniently makes it easy to change any value in memory. In fact, let's do that with the value of sum, and make sure that it returns the correct value. All you need to do is click on in the Value column of the auto or watch window and change the value. (You can also do this when the variable's value pops up while hovering over a variable in the source code.) and voila: Continuing execution of the program demonstrates that, in fact, we still get the wrong answer--this time, it returns the value 1. This suggests that truncation is taking place due to integer division. Before recompiling, we can add in a cast to solve this problem: return (double) sum / count; When to Use this TrickThis trick is powerful, and is particularly helpful when you have found one bug, but want to prove that the rest of your code will work. This is particularly handy when your debugging session requires a great deal of setup--for instance, you have to perform a lot of UI to get the right inputs or your code takes a long time to build. (An alternative would be to consider writing a unit test that demonstrates the bug and doesn't require so much setup.)On the other hand, you can't rely on this trick when you are inside a loop or a function that is called frequently--it's just too much of a pain to have to manually set variables all the time. 5. Set Next StatementSet Next Statement is a real power tool. If you're debugging, and you've accidentally (or not so accidentally) stepped past the point where something interesting happens, you can sometimes "unwind" execution. What this really means is that, maintaining the current state of the world, you can tell Visual Studio to go back and start executing from a previous instruction.You can also use set next statement to jump over code that you believe is buggy, or to jump past a conditional test on an if statement to execute a path of code that you want to debug without having to make the condition true. Setting the next statement can be dangerous and lead to instability if the stack or registers aren't in a valid state for the line being executed. And because it won't restore the state of the world, if your code depends on that state, changing the next statement might not be useful. On the other hand, if you want to make a call that you're pretty confident won't behave differently, it can be a great way of avoiding recreating a specific failure just because you accidentally stepped too far. For instance, take the following debugging state, where you're about to return the value total from getSum: If you had accidentally run too far, it might be very convenient to be able to simply say, ok, let's start that again: and then you can go back through executing the loop, perhaps setting the value of total to 0, since you notice that it wasn't initialized, and then checking to see if the program gives the correct sum (in which case, you can be pretty confident that the lack of initialization was the problem). When to Use this TrickMany of the same considerations that apply to resetting variable values also apply here. However, you should also be aware that the further ahead or back in a function you set the next statement, the more likely the debugger will not be able to compensate and your program will crash. I usually use this trick to skip over very small chunks of code, or to go back only to a single previous function call.Related articles Skip Stepping Into Functions with Visual Studio's NoStepInto Option. Debugging with Visual Studio Part 1: Debugging Concepts Debugging with Visual Studio Part 2: Setting up the Debugger Debugging with Visual Studio Part 3: Using Breakpoints Effectively Debugging with Visual Studio Part 4: Setting up Code for the Debugger Debugging with Visual Studio Part 5: Using Trace and Log Messages Debugging with Visual Studio Part 6: Remote Debugging |