Set up an editor, a compiler and GDB

A compiler on its own is a command you type. An editor turns that command into a loop: change a line, build, read the message, change it again. Everything else in this lesson is part of that loop, from the editor to the IDE that can parse the same project, and the loop is worth setting up once, before the programs get long.

The lesson uses four tools that every C++ project has: an editor, the compiler, the debugger and a build generator. Each section builds a complete program here, so the commands are the ones that ran, not a sketch of a workflow.

The edit-compile-run loop

Any editor that writes plain text works, and a terminal next to it is enough. Open a file, type the program, save it as greet.cpp, and compile from the same directory. An IDE adds completion and a debugger window, but it runs the same g++ underneath, so nothing in this course depends on the editor.

Keep one directory per experiment and one program per file. The compiler reads the file name from the command line, not from the editor, so a file saved as greet.cpp.txt fails with an error about greet.cpp, which is the first confusing message a new setup produces.

The loop has three steps and no hidden state: save, compile, run. If the compile prints nothing, the build is clean; the program then prints its own line.

#include <iostream> #include <string> int main() { std::string name = "reader"; std::cout << "Hello, " << name << "!\n"; }

g++ -std=c++23 -Wall -Wextra greet.cpp -o greet
./greet

Hello, reader!

Read the diagnostic, then fix the code

A diagnostic names the file, the line, the column and the thing that is wrong. GCC then prints the source line and a caret under the position, and often a note with the name it thinks you meant. Read the first error only: later messages are usually consequences of it.

The program below has one typo: totl instead of total. The compiler reports that totl was not declared and suggests the name that exists. That suggestion is the fix, and the corrected file builds and prints 10.

The second command in this section fails on purpose, so the listing shows the real error and then the working version. In an editor the same message is clickable: it jumps to the line the caret points at.

broken.cpp

#include <iostream> int main() { int total = 0; for (int i = 1; i <= 4; ++i) { totl += i; } std::cout << total << '\n'; }

fixed.cpp

#include <iostream> int main() { int total = 0; for (int i = 1; i <= 4; ++i) { total += i; } std::cout << total << '\n'; }

g++ -std=c++23 -Wall -Wextra broken.cpp -o broken
g++ -std=c++23 -Wall -Wextra fixed.cpp -o fixed
./fixed

broken.cpp: In function 'int main()':
broken.cpp:7:9: error: 'totl' was not declared in this scope; did you mean 'total'?
7 | totl += i;
| ^~~~
| total
10

Stop the program in GDB

A debugger answers the question the compiler cannot: what are the values while the program runs. Compile with -g to keep the source lines and variable names in the binary, then ask GDB to stop at a function and print a variable.

-batch runs the commands from the command line and exits, which fits a terminal and a script; there is no interactive prompt to learn first. break sum_to stops on entry, run starts the program, print limit shows the argument, and next steps one line.

The address in the breakpoint line changes with every build and every run, because the executable is position independent. The values do not: limit is 10, and total is still 0 on the first line of the function, before the loop adds anything.

#include <iostream> int sum_to(int limit) { int total = 0; for (int value = 1; value <= limit; ++value) { total += value; } return total; } int main() { std::cout << sum_to(10) << '\n'; }

g++ -g -std=c++23 -Wall -Wextra sum.cpp -o sum
gdb -q -batch -ex 'set disable-randomization off' -ex 'break sum_to' -ex run -ex 'print limit' -ex next -ex 'print total' ./sum

Breakpoint 1 at 0x119c: file sum.cpp, line 5.

Breakpoint 1, sum_to (limit=10) at sum.cpp:5
5 int total = 0;
$1 = 10
6 for (int value = 1; value <= limit; ++value) {
$2 = 0

Give the editor a compile_commands.json

Completion and go-to-definition need to know the compiler flags of every file, and CMake can write that list for them. Configure once with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON, and the build directory gets a compile_commands.json, a JSON file that names the compiler, the flags and the source of every target.

The file below is a complete CMake project: the minimum version, the project name, the C++ standard and one executable. The configure step detects the compiler and writes the build files; the build step runs the compiler; the last command runs the program.

The paths in the JSON are absolute and belong to this machine, so the listing does not print the file itself, only its name. An editor reads it by path; the CMake configure output shows where the build directory is.

Two values in the transcript change on every machine: the build directory in the last configure line, which is the scratch directory that built this page, and the configure time in brackets. Everything else, including the compiler identification, is what CMake reported here.

CMakeLists.txt

cmake_minimum_required(VERSION 3.20) project(toolbox CXX) set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_executable(toolbox main.cpp)

main.cpp

#include <iostream> int main() { std::cout << "built by CMake\n"; }

cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build
./build/toolbox
ls build/compile_commands.json

-- The CXX compiler identification is GNU 15.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.9s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/cppcourse/work/L003/s3/build
[ 50%] Building CXX object CMakeFiles/toolbox.dir/main.cpp.o
[100%] Linking CXX executable toolbox
[100%] Built target toolbox
built by CMake
build/compile_commands.json

Exercises

  1. Run gdb on greet.cpp with break main, then next until the program ends. Which command shows the source around the current line?
  2. Delete the build directory and configure again. Which of the four commands in the CMake section can be skipped the second time?
  3. Make the compiler print a warning about greet.cpp with -Wall -Wextra, and rewrite the program so the warning disappears.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What is the edit-compile-run loop?

    Answer

    Change a line, build, read the message, change it again

    The loop is the working rhythm of the lesson, and the editor exists to shorten each turn of it.

  2. What does -g add to a build?

    Answer

    Debug symbols that map the machine code to source lines

    GDB and the profiler read those symbols, which is why the debugging builds use -g.

  3. What does gdb -q -batch -ex run ./sum do?

    Answer

    It starts GDB, runs the program and exits after the commands

    -batch makes GDB non-interactive, and -ex supplies the commands in order.

  4. What does break sum_to do before the run?

    Answer

    It stops the program when sum_to is entered

    The breakpoint is set on the function, and the run stops at its first instruction.

  5. What does this program print? #include <iostream> #include <string> int main() { std::string name = "reader"; std::cout << "Hello, " << name << '!'; }

    Answer

    Hello, reader!

    The greeting is composed from the literal and the string, and main prints it without a newline.

  6. What does this corrected loop print? #include <iostream> int main() { int total = 0; for (int i = 1; i <= 4; ++i) total += i; std::cout << total; }

    Answer

    10

    The loop adds 1, 2, 3 and 4, and the corrected name makes the sum 10.

  7. What does GDB print for print limit at the breakpoint?

    Answer

    $1 = 10

    The parameter holds the argument 10, and GDB prints the value with its own numbering.

  8. Which file does the CMake configure write for the editor?

    Answer

    build/compile_commands.json

    The export option writes the database into the build tree, and the ls command shows it.

  9. What does the compiler say about totl += i;?

    Answer

    'totl' was not declared in this scope; did you mean 'total'?

    The name does not exist, and the compiler offers the closest declared name.

  10. Why does the editor need compile_commands.json?

    Answer

    It gives the editor the exact compiler command of every file

    With the real command the editor knows the includes, the defines and the standard, so its diagnostics match the build.

The code on this page was run with g++ 15.2.0, GDB 16.3 and CMake 4.2.3 on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev