Mastering GCC/G++/GDB: From Compilation to Debugging

Introduction: GCC/G++/GDB, The Programmer’s “Magic Wand”

Hello everyone, today we will discuss how to compile and debug C/C++ programs.

When it comes to GCC/G++, many beginners might first think: “Isn’t this just a compiler? Don’t you just write gcc main.c and hit enter?” But when compilation errors occur, they realize their understanding is as shallow as that of an ex—half-baked.

In fact, GCC/G++ is not just a tool that “converts C/C++ code into executable files”; it can also optimize, debug, troubleshoot, and even analyze code! Today, I will guide you to unlock the correct way to use GCC/G++/GDB: from compilation to debugging, let’s understand it all!

Tip:

  • GCC: C compiler, specifically for compiling C programs.
  • G++: C++ compiler, specifically for compiling C++ programs. The usage and parameters of GCC and G++ are basically the same; today we will mainly introduce GCC!
  • GDB: A powerful tool for debugging C/C++ programs!

1. What is GCC? A Brief Introduction to Its Identity

GCC, short for GNU Compiler Collection, is a powerful open-source compiler that supports multiple languages (C, C++, Objective-C, etc.). However, today we will focus solely on its performance in the C/C++ compilation field.

In a nutshell, GCC’s job: to translate the code you write from “human language” into “machine language” that machines can understand. That is, to compile your program code into machine language (binary machine code).

The core process of GCC is divided into four steps:

  1. Preprocessing: handling macro definitions, header files, conditional compilation, etc.
  2. Compilation: converting the preprocessed code into assembly code.
  3. Assembly: turning assembly code into machine code (generating object files).
  4. Linking: generating executable files from object files.

2. Installing and Checking GCC

2.1 Check if GCC is Installed

Enter the following command in the terminal:

gcc --version

If version information is returned, it means GCC is installed successfully. If it says command not found, then continue to the installation steps below.

2.2 Install GCC/G++/GDB

  1. For Ubuntu/Debian systems:
sudo apt update
sudo apt install build-essential -y   # Install gcc and g++
sudo apt install gdb                  # Install gdb

This will install GCC along with other compilation toolchains.

  1. For CentOS/Red Hat systems:
sudo yum groupinstall "Development Tools" -y  # Install gcc and g++
sudo yum install gdb -y                       # Install gdb
  1. Verify Installation: Run gcc --version, g++ --version, and gdb --version to confirm if GCC/G++/GDB are installed successfully.

3. Basic Usage of GCC: From Beginner to Proficient

3.1 The Simplest Compilation Command

gcc main.c -o main
  • main.c is your code file.
  • -o main specifies that the generated executable file should be named main. If -o is not written, it will default to generating a file named a.out.

Run the Program:

./main

It’s that simple; one command to compile and run. Isn’t that convenient? However, this is actually a “packaged” operation where compilation and linking are done together. If you’re a beginner, you might not yet know what GCC has done behind the scenes. At this point, we can try step-by-step compilation to make each step clearer.

3.2 Step-by-Step Compilation Command

Step-by-step compilation can help you better understand the workflow of the compiler. In fact, GCC compilation is divided into two main stages (G++ is similar):

  1. Compilation Stage: translating the source code into an intermediate file (object file, .o file).
  2. Linking Stage: linking the object file into the final executable file.

Step 1: Compile the Source Program File

Run the following command to convert main.c into the object file main.o:

gcc -c main.c -o main.o
  • -c option means compile only, do not link.
  • main.o is the generated object file, which cannot be run directly, but it already contains the translation results of main.c.

Step 2: Link the Object File

Next, link the object file main.o into the executable file main:

gcc main.o -o main
  • In this step, we do not use -c anymore, but use -o because we want GCC to link the object file into a complete program.

Run the program:

./main

The same output is generated through step-by-step operations; doesn’t it make you feel more professional?

Why Step-by-Step Compilation?

You might wonder: “Isn’t it troublesome to split into two steps? Can’t I just compile in one go?” In fact, step-by-step compilation has its advantages:

  • Higher Flexibility: When a project has multiple files, you only need to recompile the modified files; other parts can directly reuse the previously generated object files (.o files), greatly improving efficiency. (We will mention the situation of compiling multiple files later.)
  • Clear Process: The responsibilities of each step are clearly defined, making it easier to troubleshoot problems. For example, if a file fails to compile, it can be resolved individually without starting over.

3.3 Common Options Overview

1. Improve clarity of error messages:

gcc -Wall -Wextra main.c -o main
  • -Wall: Enable common warnings (e.g., variable declared but not used -Wunused-variable).
  • -Wextra: Enable extra warnings (e.g., unused function parameters -Wunused-parameter).

2. Prepare for debugging, add debugging symbols:

gcc -g main.c -o main
  • -g: Generate debugging information, convenient for debugging with GDB.

3. Compile multiple files:

gcc file1.c file2.c -o program
  • Multiple source files will be compiled and linked into one executable file.

4. Optimize code (-O series)

Want your program to run faster? Try optimization options:

-O0: No optimization (default)

By default, GCC does not optimize the code, and the generated program is closer to your source code.

When to use?

  • During development and debugging, it is easier to trace code logic.

Compilation example:

# The two commands have the same effect
gcc hello.c -O0 -o hello
gcc hello.c -o hello

-O1: Basic optimization

It will optimize away useless code, making the program slightly faster, but debugging remains friendly.

When to use?

  • When you need a little performance boost but still need to debug the code frequently.

Compilation example:

gcc hello.c -O1 -o hello

-O2: Common optimization (recommended!)

On top of -O1, it adds more optimizations, such as reducing loop counts and improving branch prediction.

When to use?

  • When the program runs well but you hope it runs more steadily and faster. Suitable for most scenarios.

Compilation example:

gcc hello.c -O2 -o hello

-O3: Higher-level optimization

More aggressive than -O2, enabling some advanced optimizations like function inlining and vectorization.

When to use?

  • For programs that pursue extreme performance, such as scientific computing and large data processing. But be aware, sometimes over-optimization can lead to compatibility issues (e.g., floating-point calculations may not be accurate).

Compilation example:

gcc hello.c -O3 -o hello

Summary: Choosing the Right Optimization Level

  • Development Stage: -O0 or -O1, convenient for debugging.
  • Production Environment: -O2 is the most balanced option, fast and stable.
  • Extreme Performance: -O3, but be cautious of compatibility and precision issues.

Select a suitable optimization level based on the scenario, and your code will run both steadily and quickly!

4. Compiling Multi-File Projects

In real projects, code is often divided into multiple files, for example:

  • main.c
  • utils.c
  • utils.h

Method 1: Compile All at Once

gcc main.c utils.c -o my_program

Advantages:

  • Simple and Direct: One command completes all files, suitable for small projects.
  • Quick and Convenient: Convenient and fast when there are few files.

Method 2: Step-by-Step Compilation Followed by Linking

gcc -c main.c -o main.o  
gcc -c utils.c -o utils.o  
gcc main.o utils.o -o my_program

Advantages:

  • Higher Efficiency: Only recompile modified files without recompiling everything each time.
  • More Flexible: Step-by-step compilation is better for managing larger projects and can be combined with automation tools.

Advice:

Use Method 1 when there are few files; use Method 2 when there are many files or when you want to improve efficiency. You should be proficient in both methods and switch according to the scenario!

5. In-Depth Analysis of the GCC Compilation Process: Understand Each Step

If you only know how to use GCC for compilation, that’s just the beginning; to truly understand GCC, you must know its four-step workflow.

5.1 Preprocessing: Handling Header Files and Macros First

gcc -E main.c -o main.i
  • -E: Executes only the preprocessing step, outputting the result to main.i.
  • Preprocessing will replace the contents of the header files included with #include, expand macro definitions, and remove comments.
  • Open the generated file, and you will see the “bare” code after preprocessing.

5.2 Compilation: From Human Language to Assembly

gcc -S main.i -o main.s
  • -S: Converts the preprocessed code into assembly code, resulting in main.s.
  • Assembly code is a language that sits between high-level languages and machine languages, closer to machines.

5.3 Assembly: Turning Assembly into Machine Code

gcc -c main.s -o main.o
  • -c: Executes only the step of compiling to assembly, generating the object file (main.o).
  • The object file is binary but cannot be executed directly.

5.4 Linking: Generating Executable Files

gcc main.o -o main 
  • The linker is responsible for linking the object files and system libraries together to generate the final executable file.

Comparison of File Contents:

File Name Content Type View Result with cat/vim More Suitable Viewing Method
main.i Preprocessed source code Source code, readable No additional tools needed, view directly
main.s Assembly code Assembly instructions, readable No additional tools needed, view directly
main.o Binary object file Garbage, unreadable objdump or readelf
main Executable file, machine code Garbage, unreadable objdump or readelf

PS: The usage and parameters of GCC and G++ are basically the same. To compile a C++ program, simply replace gcc with g++ in the command, for example, compile main.cpp: g++ main.cpp -o main. C++ program files usually have a .cpp suffix, such as main.cpp.

6. Debugging Tool: GDB is Here

When writing code, the worst fear is: the program crashes, but you have no idea why. This is when the debugging tool GDB comes into play.

6.1 Compile with Debugging Information Using GCC

gcc -g main.c -o main
  • -g option is mainly to generate debugging information, making it convenient for debugging with GDB.

6.2 Common GDB Commands

1. Start GDB:

gdb ./main
  • Enter GDB debugging mode.

2. Set Breakpoints:

break <line number>
  • For example, break 10 sets a breakpoint at line 10 of the code.

3. Run the Program:

run
  • Run the program, halting at the breakpoint.

4. Step Execution:

  • Step execution, without entering functions.
next
  • Step execution, entering functions.
step

Use next to skip functions, and step to enter functions as needed!

5. View Variable Values:

print <variable name>
  • For example, print x displays the current value of the variable x.

6. View Current Code (Context Code)

  • View the code at the current execution position:
list

By default, it shows the code near the current breakpoint.

  • Specify to show code near a certain line:
list <line number>

For example, list 20 shows the code near line 20.

7. Print Function Call Stack

  • View call stack information:
backtrace
  • Displays which function called the current function, tracing back to the caller, layer by layer.
  • This is especially useful for analyzing crash points (coredump).

8. View All Breakpoints:

  • List all current breakpoints:
info breakpoints

You can see the number and location of each breakpoint.

9. Delete Breakpoints:

  • Delete a specific breakpoint:
delete <breakpoint number>

For example, delete 1 deletes breakpoint number 1.

  • Delete all breakpoints:
delete

10. Continue Running the Program:

  • Continue running the program from the current breakpoint:
continue
  • The program will continue running from the current breakpoint until the next breakpoint or end.

11. Exit Debugging:

quit

7. Common Problem Troubleshooting

7.1 Missing Header Files

Error: stdio.h: No such file or directory

Reason: Simply put, the compiler cannot find the standard header file stdio.h, which may be due to the absence of the compilation tool package or missing development-related libraries in the system.

Solution:

  • On Ubuntu/Debian systems, install the necessary tool package: use the command sudo apt install build-essential to install GCC, G++, and related header files.

  • On CentOS/Red Hat systems, install development tools: sudo yum groupinstall "Development Tools". This ensures the compilation environment is complete.

7.2 “Segmentation Fault”

Error: Segmentation fault (core dumped)

Reason: Simply put, the program tries to access a memory area it shouldn’t touch, such as:

  • Using a “wild pointer” (a pointer that has not been initialized and points to an unknown address).
  • Array out of bounds, accessing the 10086th element of an array that only has 100 elements.
  • Using memory that has already been freed.

Solution:

1. Check pointers and arrays:

  • Ensure pointers are initialized, for example:
int *ptr ; 
*ptr = 10;  // Assigning value without initialization will definitely cause: Segmentation fault (core dumped)
  • Do not access elements beyond the array range, such as accessing arr[10086] when the array only has 100 elements.
  • If using dynamic memory allocation (malloc/free), check if it has been freed twice.

2. Use GDB for debugging:

  • Compile with the -g option to generate debugging information:
gcc -g main.c -o main
  • Then run the program with GDB:
gdb ./main
run
  • When the program crashes, enter:
bt

GDB will tell you where the error occurred.

Don’t panic during debugging; find that line of code, fix it slowly, and you can resolve the segmentation fault!

7.3 Linking Errors

Error: undefined reference to ‘some function’

Reason: This means that your code uses a function, but the compiler cannot find its implementation during the linking phase. This may be due to:

  • Forgetting to include the implementation file: the function is written in another .c file, but it was not included during compilation.
  • Missing library linkage: using a function from an external library but failing to inform the compiler which library to use.
  • The function declaration is fine, but the function implementation was not written, so the compiler does not know where to find it.

Solution:

  • If the function is written by you, ensure that all related files are included during compilation:
gcc main.c func.c -o main

If it wasn’t included, add it!

  • If it’s a library function, such as the sqrt from the math library, you need to link the corresponding library by adding -lm:
gcc main.c -lm -o main

Here, -lm indicates linking the math library.

  • Check if the function implementation is indeed written! If only the function is declared:
void my_function();

But you forgot to write the implementation, it will definitely cause an error. Quickly add the implementation!

This linking error is quite common, and careful checking of files and libraries can resolve it!

7.4 Undefined Function Reference

Error: undefined reference to ‘some function’
Reason: Simply put, this error means that your code uses a certain function, but the compiler cannot find its implementation. This may be because you did not include the implementation file in the compilation command, or you used an external library but forgot to link it.
Solution:
1. If it’s a function you wrote yourself, make sure its implementation file is included in the compilation command, for example:
gcc main.c func.c -o main

If it wasn’t included, add it!

2. If it’s a library function, such as sqrt from the math library, add the corresponding library linkage, for example:

gcc main.c -lm -o main

This -lm tells the compiler, “I want to use the math library.”

7.5 GDB Prompts No Debugging Information

Error: No debugging symbols found
Reason: This error simply means: your program does not contain “debugging information.” GDB says, “You want me to debug the code, but you didn’t give me the map (debugging information), how can I know where the problem is?”
Solution:
Remember to add the -g option during compilation to let GCC package the debugging information, for example:
gcc -g main.c -o main

This -g is the “map.”

Then debug with GDB:

gdb ./main

Now GDB knows how the code flows and can help you find the problem!

8. Summary: GCC/G++/GDB is Not Magic, But Mastering Them is Like Cheating

GCC/G++ is like a “Swiss Army Knife” in development, comprehensive yet not complicated; once you master them, your development efficiency will skyrocket:

  • From compilation to optimization, easily handle it, making programs fast and stable.
  • From debugging to troubleshooting, with GDB, analyzing problems becomes clearer and more intuitive.
  • Mastering GCC/G++ and GDB makes compiling and debugging any C/C++ program a breeze!

After reading this article, do you feel that the usage of GCC/G++ and GDB is clearer? Hurry up and practice; practice is the quickest way to master these tools!

Autumn recruitment has ended, and if everyone does not prepare adequately, it will be difficult to find a good job in the spring recruitment.

Here’s a job hunting gift package for everyone, you can prepare for the spring recruitment and find a good job!

Mastering GCC/G++/GDB: From Compilation to Debugging

Leave a Comment