C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
192: Debugging with gdb
You've probably spent a fair amount of time by now using printf to debug your code. We've all been there—sprinkling "I am here" messages every three lines just to figure out where a pointer went sideways. It works, but it's slow and clutters your code. That's why we use GDB (the GNU Debugger). It lets you freeze time, peek inside your variables, and step through your logic one line at a time.
How do I actually get GDB to stop where I want?
First things first: GDB can't tell you which line of code is running unless you tell the compiler to include "debug symbols." If you don't use the -g flag when compiling, GDB will just show you memory addresses like 0x004005d1, which is useless to us. Always compile like this:
gcc -g my_program.c -o my_program
Once you've done that, start the debugger with gdb ./my_program. Now, you don't want to just run the program and hope for the best; you want to set a breakpoint. If you have a function called process_data where you suspect the bug is, just type break process_data. If you know it's specifically on line 42, use break 42.
Then, type run (or just r). The program will execute at full speed until it hits that breakpoint, then it'll freeze. Now you're in the driver's seat.
My program just segfaulted—how do I find the exact line?
This is where GDB really earns its keep. Instead of guessing where the crash happened, just run the program inside GDB without any breakpoints: run. When the program hits that dreaded Segmentation Fault, GDB will stop immediately and tell you exactly which line caused the crash.
But the crash might be happening inside a library function (like strlen) because you passed it a NULL pointer. In that case, the current line isn't the problem—the function that called it is. This is where you use the backtrace command (or bt). It shows you the call stack: the sequence of function calls that led to the crash.
# Example GDB output
(gdb) bt
#0 0x00007ffff7a43c2b in __strlen_avx2 () from /lib64/libc.so.6
#1 0x00000000004006a1 in print_name (student=0x0) at main.c:12
#2 0x00000000004006e5 in main () at main.c:20
Looking at this, I can see the crash happened in strlen, but the real mistake is on line 12 in main.c, where I passed a NULL student pointer to print_name. I can use frame 1 to jump back to that specific function's context and inspect the variables there.
How do I track a variable that's changing when it shouldn't?
You know the feeling: a variable is 10 at the start of a loop, and suddenly it's -128492, but you have no idea which line changed it. You could print x (or p x) every other line, but that's tedious.
Instead, use a watchpoint. While your program is paused, type watch my_variable. GDB will now let the program run, but it will automatically freeze execution the very instant the value of my_variable changes. It's essentially a "breakpoint for data."
While you're stepping through, remember the difference between next (n) and step (s). next executes the current line and moves to the next one in the current function. step, however, will dive into any function call on that line. I usually use next unless I'm specifically suspicious of a helper function I wrote.
📋 Practical Task
Debugging the Broken Linked List Traversal
You have been given a small program that manages a list of high scores. The program is crashing with a Segmentation Fault, but the developer who wrote it didn't leave any comments. Your goal is to use GDB to find the bug and fix it.
The Code:
# scores.c
#include <stdio.h>
#include <stdlib.h>
struct Node {
int score;
struct Node* next;
};
void print_scores(struct Node* head) {
struct Node* current = head;
while (current->score != -1) { // Hint: Is this the right way to stop?
printf("Score: %d\n", current->score);
current = current->next;
}
}
int main() {
struct Node* n1 = malloc(sizeof(struct Node));
struct Node* n2 = malloc(sizeof(struct Node));
n1->score = 100;
n1->next = n2;
n2->score = 200;
n2->next = NULL;
print_scores(n1);
return 0;
}
Your Task:
- Compile the program with debug symbols:
gcc -g scores.c -o scores. - Run the program inside GDB (
gdb ./scores) and use theruncommand to trigger the crash. - Use
backtraceto identify the exact line causing the Segmentation Fault. - Use
print currentto see the value of the pointer at the moment of the crash. - Fix the
whileloop condition inprint_scoresso that it correctly handles the end of the list (wherenextisNULL) instead of checking for a sentinel value of -1. - Recompile and verify that the program now prints both scores and exits gracefully.
There are no comments for now.