Install a C++ toolchain on Linux

A C++ program starts as text and ends as a file the kernel can run. Between the two sit a compiler, a linker and a standard library, and this lesson puts that set on Linux, checks the version that arrived, and builds a first program. The same commands work on a desktop, in a Docker container and on a server, which is why the rest of the course uses them.

The C++ course assumes nothing but a terminal and the package manager. Every page shows the source, the exact command that built it and the output that came back.

The four programs in a toolchain

A toolchain is more than one program. g++ is the driver: it preprocesses a .cpp file, compiles the result to machine code, and calls the linker to join object files and the standard library into an executable. GCC is the project that ships g++, libstdc++ and the C++ headers, and every Linux distribution packages them as one set.

The version macros are the first thing to print on a new machine. If they come out, the compiler, its headers and the linker agree with each other, and the rest of the course will build.

#include <iostream> int main() { std::cout << "compiler version: " << __VERSION__ << '\n'; std::cout << "standard: " << __cplusplus << '\n'; }

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

compiler version: 15.2.0
standard: 202302

Install the packages

One package command is enough. On Alpine Linux the package build-base brings g++, make, the linker and the C library headers; gdb and cmake are separate packages. On Debian and Ubuntu the same set is build-essential plus gdb and cmake. The names differ, the compiler does not.

Neither package set installs an editor, and none is required to follow a lesson. A plain terminal and a file saved with any editor are enough; the next lesson adds the tools that make editing faster.

The program below uses std::string and std::vector, so it proves that the headers, the C++ standard library and the linker are all in place, not just the compiler binary.

#include <iostream> #include <string> #include <vector> int main() { std::vector<std::string> tools{"g++", "make", "cmake", "gdb"}; for (std::size_t i = 0; i < tools.size(); ++i) { std::cout << tools[i]; if (i + 1 < tools.size()) { std::cout << ", "; } } std::cout << '\n'; }

apk info -e build-base g++ make cmake gdb
g++ -std=c++23 -Wall -Wextra sanity.cpp -o sanity
./sanity

build-base
g++
make
cmake
gdb
g++, make, cmake, gdb

Compile and run

Save the program as hello.cpp and compile it. -std=c++23 selects the language version and -o names the output file; without -o the compiler writes a.out and you have to guess which program you just built.

./hello tells the shell to run the file in the current directory, because the current directory is usually not on PATH. Typing hello alone prints a command not found error even though the file is right there.

The Bash prompt returns the exit status of the program, which is 0 here. Everything worked: the compiler read the source, the linker found the standard library, and the binary ran.

#include <iostream> int main() { std::cout << "Hello from a Linux toolchain!" << '\n'; return 0; }

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

Hello from a Linux toolchain!

Ask for warnings

An unused parameter is a bug in waiting: the caller passes a height and the function quietly ignores it. With no flags, g++ compiles that file without a word, and the wrong result ships.

-Wall -Wextra turns on the useful warning groups. The compiler then names the parameter, the file, the line and the flag that reported it, which is usually enough to see the typo without a debugger.

The first command compiles the same file without warnings and prints nothing. The second prints the warning and still produces a binary, because a warning is not an error. The program then prints the wrong area: 9 instead of 12.

#include <iostream> int area(int width, int height) { return width * width; } int main() { std::cout << area(3, 4) << '\n'; }

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

warning.cpp: In function 'int area(int, int)':
warning.cpp:3:25: warning: unused parameter 'height' [-Wunused-parameter]
3 | int area(int width, int height)
| ~~~~^~~~~~
9

Pin the standard

The compiler is only as modern as the standard you ask for. Without -std, g++ 15 compiles as gnu++17, and __cplusplus reports 201703. The same file built with -std=c++23 reports 202302, and only then do the C++20 and C++23 headers work.

The macro is a useful probe: a library that needs C++20 usually fails on a missing header or a missing type, and the first thing to check is the value printed here. The flag belongs in the build command, not in the source file.

#include <iostream> int main() { std::cout << __cplusplus << '\n'; }

g++ -Wall -Wextra standard.cpp -o standard17
./standard17
g++ -std=c++23 -Wall -Wextra standard.cpp -o standard23
./standard23

201703
202302

When the build stops

Two errors catch almost every first build. The first is a wrong file name: an editor that saves hello.cpp.txt, or a typo on the command line, leaves the compiler with nothing to read.

The second is a standard that is too old. The program below stores a C array in std::span, a C++20 type, and the command asks for C++17. The message comes from the header guard inside <span> and names the standard it needs.

Both messages end with compilation terminated or with a count of errors, and neither leaves a binary behind. Recompile with the right name and -std=c++23 and the same source builds.

#include <iostream> #include <span> int sum(std::span<const int> values) { int total = 0; for (int value : values) { total += value; } return total; } int main() { int numbers[]{1, 2, 3, 4}; std::cout << sum(numbers) << '\n'; }

g++ -std=c++23 helo.cpp -o helo
g++ -std=c++17 -Wall -Wextra span.cpp -o span

cc1plus: fatal error: helo.cpp: No such file or directory
compilation terminated.
span.cpp:4:14: error: 'span' is not a member of 'std'
4 | int sum(std::span<const int> values)
| ^~~~
span.cpp:4:14: note: 'std::span' is only available from C++20 onwards
span.cpp:4:19: error: expected primary-expression before 'const'
4 | int sum(std::span<const int> values)
| ^~~~~
span.cpp: In function 'int main()':
span.cpp:16:21: error: 'sum' cannot be used as a function
16 | std::cout << sum(numbers) << '\n';
| ~~~^~~~~~~~~

Exercises

  1. Print __VERSION__ with g++ and with clang++ if you have it, and compare the two strings.
  2. Compile hello.cpp without -o and list the directory. Which file appeared, and what does file say about it?
  3. Compile warning.cpp with -Wall -Wextra -Werror. What changes in the output, and why is no binary produced?

Questions

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

  1. What does the linker do in the build?

    Answer

    It joins the object files and the libraries into one executable

    The compiler produces objects, and the linker is the program that resolves their symbols and writes the executable.

  2. What does -std=c++23 select?

    Answer

    The language standard the compiler accepts

    The flag chooses the language version, which decides which features and which diagnostics the compiler applies.

  3. What do -Wall -Wextra do?

    Answer

    They enable warning groups, and the program is still built

    Warnings are diagnostics about legal code; only -Werror turns them into a failed build.

  4. Why does the course pin the standard in every command?

    Answer

    So the same source builds under the same rules on every machine

    The default standard depends on the compiler version, and the flag makes the lesson reproducible.

  5. What does this program print? #include <iostream> int main() { std::cout << "compiler version: " << __VERSION__ << " and standard: " << __cplusplus << '\n'; }

    Answer

    compiler version: 15.2.0 and standard: 202302

    __VERSION__ is the compiler's version string and __cplusplus is 202302 under -std=c++23.

  6. The program is built with g++ -std=c++23 -Wall -Wextra warning.cpp and run. The compiler warns about the unused parameter height. What does the program print? #include <iostream> int area(int width, int height) { return width * width; } int main() { std::cout << area(3, 4) << '\n'; }

    Answer

    9

    A warning does not stop the build, and the function returns 3 times 3 without using the parameter.

  7. What does ./standard17 print?

    Answer

    201703

    The binary was built without -std, so the compiler used its default, and the program prints __cplusplus as 201703.

  8. What does the first build on the page print when it runs?

    Answer

    Hello from a Linux toolchain!

    The hello program of the third section prints that line, and the toolchain has just built it.

  9. What does the compiler say when the source file is missing?

    Answer

    cc1plus: fatal error: helo.cpp: No such file or directory

    The driver passes the file to cc1plus, which reports the missing input before any parsing.

  10. This file is compiled with g++ -std=c++17. The compiler rejects it. What does it say? #include <span> std::span<const int> values;

    Answer

    an error that 'span' is not a member of 'std', with a note that it needs C++20

    The type is gated by the language standard, and the note names C++20, which introduced std::span.

The code on this page was run with g++ 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev

Banner image