compiling with debug info
When the compiler turns your readable source code into machine code, it normally throws away everything that was only for humans: your variable names, which line each instruction came from, the shape of your structs. The CPU does not need them. But a debugger desperately does - without them it can only show you raw addresses and hex. Compiling with debug info means asking the compiler to also keep a side-table of all that human-friendly mapping, so a debugger can translate the machine back into your terms.
Concretely, you pass the -g flag to the compiler: 'gcc -g main.c' or 'clang -g main.c'. This embeds debug information (in a format called DWARF on Linux) into the executable - a map from each machine instruction back to its source file and line number, from each storage location back to its variable name, and a description of every type. With this, a debugger can say 'you are at main.c line 42, variable count holds 7' instead of 'you are at address 0x401136, the value at 0x7fffffffe0a4 is 0x07'. Importantly, -g does NOT slow your program down or change what it computes - it only adds information; the running code is the same. It does make the executable file bigger, which you can strip later for shipping while keeping a separate symbol file.
Why it matters: -g is the difference between a debugger, a backtrace, or a core dump being readable versus being a wall of hex addresses, so it is the price of admission for almost all source-level debugging. The tension is with optimization, a separate axis: with -O2 the compiler reorders, merges, and deletes code, so even with -g the debugger may report '<value optimized out>' or jump around lines confusingly. The common compromise while hunting a bug is 'gcc -g -O0' (debug info, optimization off) for a faithful debugging experience, then build with optimization for release - but remember a bug that only appears under -O2 will not reproduce under -O0.
Compare: gcc main.c -o a then a crash shows only addresses in gdb. But gcc -g -O0 main.c -o a lets gdb show real line numbers and variable names, e.g. 'Segmentation fault in parse (s=0x0) at parse.c:30'. Same program behavior, vastly more readable debugging.
-g adds source-level info without changing behavior; -O0 keeps it from being optimized away.
Debug info (-g) and optimization (-O) are independent: -g does not disable optimization, and an optimized build with -g can still show '<optimized out>' for variables the compiler removed. A bug that only appears under -O2 may genuinely vanish at -O0, which is itself a strong hint of undefined behavior the optimizer is exploiting.