I recently upgraded to Windows 11 and had to set up Visual Studio C++ again. A few things have changed in Windows 11 with the tiled window management and deeper Windows Terminal integration, so I ended up revisiting my usual VS setup and made a bunch of changes. I'm documenting them here for my own future benefit (yes, I do know about VS import/export settings) and on the off chance that anyone else might get some ideas for their own setup.
It should be mentioned that I'm a single monitor user (multimon gives me neck pain as I've gotten older) and the monitor I currently use is 25". A laptop screen can't really accommodate the 2x2 full screen layout I'm using as my default here, so when I'm on a laptop I only use the vertical layout out of necessity.
It goes without saying that step 1 with a fresh VS install is to turn off a bunch of crap that only gets in your way and hurts editor performance, and after that you want to set up all the coding style options so you don't lose your sanity trying to fight the auto-formatting. I won't bother documenting that in any detail; just spend 10 minutes combing through all the settings. If something sounds useless or I know it to be a problem, I turn it off entirely; I personally turn off everything related to IntelliSense and use Visual Assist X to replace its features. If you don't want to pay for a VAX license but still want those kinds of features, you can leave it on.
Alright, onward to the description of my setup. First some screenshots:
Note that these were taken before a couple of the minor tweaks mentioned below were made (e.g. the font sizes for the debug tool windows are still set to the default in those screenshots).
Full screen layout (edit):
Editor window takes up everything (split into two side-by-side documents when needed). The breakpoints, error list and output log are auto-hide tabs on the bottom and can be shown with alt-o (output), alt-e (errors), etc.
Full screen layout (debug):
Asymmetric 2x2 layout favoring editor window (enough room for long lines). Movement between windows is mouseless, driven by the alt-based key bindings below. The three debug windows each have several tabs organized by functionality and related/exclusive uses, and also by which debug tabs favor horizontal vs vertical screen real estate.
Vertical layout (edit/debug):
Designed for Windows 11 tiled windows with the VS window on the left and the app or terminal window on the right. In debug mode, all the debug tabs are consolidated in one window at the bottom. You can configure Windows Terminal in Windows 11 so all console apps start as a new tab in an existing Windows Terminal instance, which is handy to keep a stable layout when restarting during iteration.
Debug workflow:
I live in the debugger when I'm writing code. A debugger's aspiration is to be a universal tool for understanding and manipulating computer programs in motion.
Too many things to cover but here's a random assortment of tips. Some of these key bindings are custom; you can find a complete list at the end of this article.
- You can universally switch focus to the active document with ctrl-q. Teach yourself to stop using esc to defocus tool windows since not all of the debug-related windows respect it.
- Use alt-q to toggle between the active document window and the disassembly window. A key use case is that toggle disassembly will not only switch focus to the debug window if you're currently in the editor window, but it will show the disassembly for the code at your current cursor position, independent of the instruction pointer. This makes it easy to see assembly code for functions like you might do with godbolt.org's Compiler Explorer but this is in the context of a running program.
- Learn to love ctrl-f10 (run to cursor). This might sound hyperbolic but it's probably the most important step or breakpoint-like command for me. Like all the debug step functions it will automatically recompile and run the program first if you're currently in edit mode, which is very comfortable and efficient for rapid iteration. A "missing" command in VS is restart to cursor which combines ctrl-shift-f5 (restart program under debugger, will recompile changes first) and ctrl-f10 into a single action. I did a tiny VSIX extension for that, which I bound to ctrl-shift-f10.
- The immediate window (alt-i with my bindings) is an exploratory REPL for expression evaluation when you don't want to deal with constantly adding/removing/changing persistent watches. If you want to evaluate a side-effecting expression like an assignment or a function call, you should probably do it here. It supports all the same expression syntax and format specifiers that you get in the watch window. The buffer history is persistent across runs and it has a persistent readline-style command history that can be navigated with the up/down arrow keys. The immediate window also doesn't lose focus if you resume execution with the step/run commands, so you can alternate stepping and evaluating new expressions without having to constantly switch window focus.
- Set up the memory window with explicit columns (I normally use 8) rather than the "auto" default which sets columns based on the memory window's width. In my case auto gives me 5 columns, which is worse than useless. You usually want to see the natural power-of-two alignment of data in the grid pattern. If you have an array of structs with some particular size, you can set the number of columns to reflect that, but I find that 8 is the best default.
- VAX offers an alternative memory window with complementary strengths and weaknesses. It's less of a grid-oriented hex editor view of memory and therefore not as helpful for visual inspection and pattern matching, but its advantage is that it will automatically try to symbolize values in memory that look like pointers. This can be useful, for example, if you have a corrupted stack that the debugger can't make sense of and you want to piece together the call chain. But if you want power-user features like this, you should really be using WinDbg (e.g. the dds command). WinDbg is a totally different beast which has a terrible workflow for iterative development, but it's probably the best post-mortem debugger available on any platform; you'll want to master it eventually, but you'll know when you need it. I'm not a WinDbg pro by any means, so historically my two main use cases have been automated analysis and triage of crash dumps with !analyze (quick and easy) and as a last resort debugging otherwise intractable post-mortem crashes (slow and painful).
- My default memory window expression is ($rsp - 64) & ~7 which provides simultaneous visibility of both the current stack frame and the stack frames of any recently returned functions. The reason for the & ~7 is a corollary of the previous bullet point: you want your addresses to be aligned to the 8-byte visual grid. Also, make sure you toggle "Reevaluate automatically".
- Even though my primary memory tab (bound to alt-m) is in the upper right window (same as the disassembly tab) and my primary watch tab (bound to alt-w) is in the lower left window (same as the locals tab), I've populated the three debug windows with their own unique instance of the watch and memory tabs. This is a handy way to adapt the layout on the fly to what the situation demands without physically reorganizing the existing tabs. Note that these are unique instances (i.e. they monitor different watch and memory expressions) rather than duplicate views of the same tab.
- These extra watch windows are very versatile. If you want a really long list of watch expressions you can use as a dumping ground, you can stick them in the upper right window to give them extra space while keeping the lower left window on the locals tab, for example; I like to reserve the default watch window for a smaller, curated set of expressions that I can edit and visually scan at a glance. The smallest, lower-right watch window I usually set up with long-term "global" watch expressions, so I can always see their values there if needed.
- Admittedly I get much less mileage from the extra memory windows but they're still useful in a pinch. My main recurring use case is setting up spare memory tabs to visualize arrays of structs: setting 'columns' to the struct size lets you see relationships vertically like you would in a database table.
- The default font sizes for the different tool windows are all over the place (either too small or too large for my taste) so you probably want to give them a once-over to suit your preferences.
- Learn all the expression evaluator's pseudovariables and format specifiers:
Custom key bindings:
-
F1 - Full screen layout
-
F2 - Vertical layout (VS on left side, terminal/program on right)
-
alt-q - Debug: Toggle disassembly
-
alt-d - Debug: Disassembly
-
alt-a - Debug: Autos
-
alt-s - Debug: Call stack
-
alt-w - Debug: Watch
-
alt-l - Debug: Locals
-
alt-r - Debug: Registers
-
alt-m - Debug: Memory
-
alt-i - Debug: Immediate window
-
alt-b - Debug: Breakpoints
-
alt-e - Debug: Edit value (autos/locals/watch window)
-
alt-e - View: Error List (global)
-
alt-o - Debug: Output
-
alt-1 - Debug: Watch 1
-
alt-2 - Debug: Watch 2
-
alt-3 - Debug: Watch 3
-
ctrl-q - Activate document
-
ctrl-w - Close active document
-
ctrl-/ - Toggle line comment
-
ctrl-o - VAX open file
-
ctrl-shift-o - VAX open related file
-
ctrl-p - VAX find symbol
-
ctrl-[ - VAX navigate back
-
ctrl-] - VAX navigate forward
-
ctrl-. - VAX goto implementation
-
alt-. - VAX goto related
-
ctrl-, - VAX smart extend select
-
alt, - VAX smart extend select block
-
alt-enter - VAX open context menu
-
ctrl-r, ctrl-r - VAX rename
-
ctrl-shift-f10 - Restart to cursor (my own VSIX package, ctrl-shift-f5 + ctrl-f10 in one)