It is not the debugger nor the method of debugging that matters as much as obtaining the pertinent information and understanding what is happening, that matters.
My favourite example of this is when you have a linked list, tree, or graph, and something goes b0rk.
If you reach for a debugger first, I feel for you. Sure, debuggers like gdb nowadays are
quite extensible, so you can teach them about your data structures so you can query the application state quite effectively, but it is a lot of detective work to find out what exactly did go b0rk with a debugger.
What I do, is have a function emit the structure description in Graphviz dot language to a file, and visualize that. For example, if I have a binary search tree,
struct node {
struct node *le;
struct node *gt;
/* Some payload, say a string: */
char *data;
};
I habitually write a debugging function for it, something along the lines of
static void debug_tree_node(FILE *out, const struct node *node)
{
fprintf(out, " \"%p\" [ label = \"%s\" ];\n", node, node->data);
if (node->le) {
debug_tree_node(file, node->le);
fprintf(out, " \"%p\" -> \"%p\" [ taillabel = \"<=\" ];\n", node, node->le);
}
if (node->gt) {
debug_tree_node(file, node->gt);
fprintf(out, " \"%p\" -> \"%p\" [ taillabel = \">\" ];\n", node, node->gt);
}
}
void debug_tree(FILE *out, const struct node *tree)
{
fprintf(out, "digraph {\n");
if (tree) debug_tree_node(out, tree);
fprintf(out, "}\n");
}
While this is obviously a form of "printf debugging", displaying the generated dot graph description via
dot -Tx11 output is absolutely indispensable in understanding
how things went b0rk, so that one can start finding out
why.
(For learners, I actually prefer a more complicated version, that tracks nodes either via a dedicated "visited" field in the structure, or by putting the node addresses in a hash table. That way the debugging output won't get dazed and confused by cyclic graphs and such.)
When a student sees the (incorrect) tree/graph their code generates, in my experience they are more likely to understand
what they did wrong.
The key, of course, is to first understand the
b0rk first. And for that, I claim it does not matter what debugging tool you use, as long as you are efficient about it.
The point where I fire up my debugger, is when I need to know what is happening at the machine code level. Usually, when I just want to track certain variables etc. in real time in a multithreaded process, I use a dedicated thread to "printf-debug" their changes. In a couple of cases, I've written my own "debugger" using the ptrace interface, to ensure minimal interference with the target process (Heisenbugs, 'nuf said). See e.g.
here for an example one for multithreaded processes I wrote in 2013.
It is perfectly okay if you disagree, but for me, these patterns have proven their worth in real life, both for myself, and as an educational tool, helping others learn. I'm not writing this to change anyones mind, but to explain the practical reason and experience behind my opinion.