To add to this: not only does it allow you to step through and follow the control flow of a program. In some hairy cases there are issues which are very difficult to answer with printf debugging.
Let's say a variable is being overwritten and you can't figure out why. It could be some dark corner of the code you missed or it could even be memory corruption. A debugger will let you put a conditional breakpoint on that memory address being written, which will then give you all the context needed to see what's going on. This has helped me solve quite a few really tricky bugs.
There's also the simple case of debugging a tricky crash. Before learning to use a debugger (some twenty years ago) I had to do printf with all kinds of local variables guessing at what might be the cause. Attaching a debugger you mostly just run the program, it gets an access violation, and the debugger breaks showing you the generated exception, the stack, the exact statement which caused the crash (though this mostly requires debugging with optimizations turned off), and you can just hover over any variable to check their values. Most crash bugs which would have been very tricky with printf now take seconds to diagnose because you have the full context of the program just as it was when it crashed, right at your fingertips.
Since this is a comment on a shader program there's also the matter of GPU debuggers which I just have to mention in order to say: you can't really do typical printf debugging for most shaders and graphics debuggers are like magic.
Let's say a variable is being overwritten and you can't figure out why. It could be some dark corner of the code you missed or it could even be memory corruption. A debugger will let you put a conditional breakpoint on that memory address being written, which will then give you all the context needed to see what's going on. This has helped me solve quite a few really tricky bugs.
There's also the simple case of debugging a tricky crash. Before learning to use a debugger (some twenty years ago) I had to do printf with all kinds of local variables guessing at what might be the cause. Attaching a debugger you mostly just run the program, it gets an access violation, and the debugger breaks showing you the generated exception, the stack, the exact statement which caused the crash (though this mostly requires debugging with optimizations turned off), and you can just hover over any variable to check their values. Most crash bugs which would have been very tricky with printf now take seconds to diagnose because you have the full context of the program just as it was when it crashed, right at your fingertips.
Since this is a comment on a shader program there's also the matter of GPU debuggers which I just have to mention in order to say: you can't really do typical printf debugging for most shaders and graphics debuggers are like magic.