Summary
C++ has a notoriously complicated compilation model. It can be an ordeal to even get a C++ project to compile. It is even harder to configure one to produce debuggable binaries. I want to help regular C++ programmers improve their debugging experiences. Follow this link to skip the intro text and jump directly to the advice.
Interactive debugging is most useful in large unfamiliar legacy projects written in familiar languages. Printf-style debugging is oddly attractive in C++ simply because you can generally expect it to work. It is usually possible to generate highly-debuggable C++ binaries that work well with an interactive debugger without sacrificing too much performance.
In this week's Daily Discussion, we look at some of the most important issues of the day. This week, we discuss the issue of the best way to make the most of the time we have left on this planet. We also look at ways to improve the quality of our communication.
The sanitizers are not all compatible with each other, so you will need to test multiple builds with different subsets enabled. There is currently no production-quality C++ toolchain that promises to alert on all undefined behavior. Correctness issues from undefined behavior are currently an unavoidable risk.
If using libc++: Add this define to your CXXFLAGS if you are able to recompile your dependencies from source. If using libstdc++: -DLIBCPP_HARDENING_MODE=_LIBPP HARDENing_MODE_DEBUG.
This will make printing backtraces faster and more reliable. This made sense for release builds on 32-bit x86. In practice, this never worked very well. For x86_64, there are many more registers and it is worth it to always include the frame-pointer.
Ref Link with whole-archive so that the entire static archive is available. Historically, reverse debuggers have not supported all x86_64 instructions (e.g. AVX). x86-64 is the baseline 64-bit x86 architecture without extensions.
C++ is often used for code that has to be fast. Unoptimized C++ code can be very slow, especially in large projects. It is possible to link together optimized and unoptimized TUs. This will always make our backtraces more informative.
Confusingly, gdb will suggest that the function "may have been inlined", when the actual problem is that the template member function was never generated in the first place. G++ recommends using -Og instead of -O0 for the best debugging experience.
Some classes cannot be explicitly instantiated for all valid arguments. Be aware that it may be a bad idea to leave explicit ifdefs in your code. Run unifdef to evaluate preprocessor ifdef. Run ifdef to run in-place on a subset of your codebase.
If you want to use a new macro, you need to add a new line of code to the top of the macro. If you want the old line to stay the same, you must add the new line to the bottom of the current line. If the newline is not the same as the old one, you have to add another line.
The way that gdb sets a breakpoint is to temporarily replace an instruction with the single byte int $3 instruction. This can be very slow because it only requires overwriting a single byte of the binary, which it knows how to do safely. Using volatile means that the program will always read the variable from memory before using it.
Gdb can be configured to always step-over arbitrary files and directories. It can also be used to blacklist code that we don't usually want to step-into. For example: the stdlib, third-party dependencies, utility classes, custom string or enumeration classes.
Use the gdb Python API to write pretty-printers for your project's frequently-used classes. Run the following command inside gdb while attached to your running process to see if thepretty-printer is installed and available. This is analogous to writing a custom str function on a Python object.
Thank you to Eliot Robson for providing feedback on drafts of this post. All mistakes are my own. C++20 concepts are the partial solution to template hell for compilers. They complement C++15 concepts, which are the full solution to the template hell problem.