AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

01 C++ and the toolchain / Lesson 5 of 5

Compilers, warnings and reading error messages

14 min · +10 XP · 2 exercises · 7 questions

Read the text14 min
Exercises0/2
Quiz7 q

Quick answer

How do you read a C++ compiler error?

A C++ compiler error has the form file:line:col: error: message, followed by the source line, a caret under the offending token and any note: lines that give context. Fix the first error first, because the errors after it are often its consequences, and read the notes. For a template error, the first line says what failed and the required from here line names your code.

A C++ compiler is the most thorough reviewer your code will ever have, and it works for free — if you read what it says. This lesson teaches the shape of a diagnostic so that you can read it like a stack trace: which file, which line and column, what kind of message, and what the notes underneath are pointing at. It also covers the warning flags worth turning on, what a template error and a linker error look like, and the two run-time tools — sanitizers and a debugger — that catch what the compiler cannot.

The three compilers

CompilerDriverWhereNotes
GCCg++Linux default, MinGW on WindowsShips libstdc++, the standard library most Linux C++ uses
Clangclang++macOS default (Apple Clang), Linux, WindowsLLVM-based; famously readable diagnostics; uses libstdc++ or its own libc++
MSVCclWindows, Visual StudioDifferent flag syntax (/W4, /std:c++20) and a different message format

GCC and Clang accept the same flags and produce diagnostics in the same file:line:col: kind: message shape, so what you learn on one transfers. The study judge is Clang 18 with libstdc++ 14, -std=c++20 -O2; the local gate this track was written against is GCC 14 with -Wall -Wextra. Code that is clean on either compiles on the other unless it leans on a compiler extension — variable-length arrays, __int128, #include <bits/stdc++.h> — which is why the track avoids them.

Anatomy of a compile error

#include <iostream>

int main() {
    int total = 0;
    for (int i = 0; i < 3; ++i) {
        totl += i;
    }
    std::cout << total << '\n';
}
main.cpp:6:9: error: use of undeclared identifier 'totl'; did you mean 'total'?
    6 |         totl += i;
      |         ^~~~
      |         total
main.cpp:4:9: note: 'total' declared here
    4 |     int total = 0;
      |         ^
1 error generated.

Read it left to right. main.cpp:6:9 — file, line 6, column 9; every editor can jump there. error — the severity: an error stops the build, a warning does not, a note is not a problem at all but context for the message above it. Then the message, often with a suggestion. The source line and the caret show exactly which token, and the note: says where the related declaration is. 1 error generated. is Clang's summary; GCC ends with nothing.

Two habits: fix the first error first, because one missing semicolon or unknown type cascades into dozens of nonsense errors below it; and read the notes, because for an overload or template failure the note chain (candidate function not viable: ...) is the actual explanation.

Warnings, and which flags to turn on

A warning is the compiler saying "this is legal and probably wrong". -Wall enables the ones almost always worth fixing; -Wextra adds a second tier; -Wpedantic complains about extensions the standard does not allow; -Werror turns every warning into an error, so a build with warnings fails — which is how teams keep the count at zero.

Warning (flag)What it catchesWhy it matters
unused variable (-Wunused-variable)a name declared and never readusually a typo elsewhere, or dead code
may be used uninitialised (-Wuninitialized, -Wmaybe-uninitialized)a read before any writeundefined behaviour; initialise with {}
control reaches end of non-void function (-Wreturn-type)a missing return on some pathundefined behaviour, and -O2 makes it bite
comparison of integers of different signs (-Wsign-compare)i < v.size() with int ithe int converts to unsigned; -1 < v.size() is false
unused value (-Wunused-value)x + 1;a statement with no effect
-Wshadow (not in -Wall)an inner variable hiding an outer onethe bug from lesson 3
-Wconversion (not in -Wall)int x = 3.7;, long into intsilent narrowing

Build with -Wall -Wextra from day one. A warning you have decided is harmless is noise that hides the next real one; fix it, or silence that one case deliberately, never by lowering the flags.

A template error

Template errors are long because the compiler reports the failure where it happened — deep in the library — and then the chain of instantiations that led there. Sorting a vector of a type that has no <:

/usr/include/c++/14/bits/predefined_ops.h:45:23: error: no match for 'operator<' (operand types are 'const Point' and 'const Point')
   45 |       { return *__it1 < *__it2; }
      |                ~~~~~~~^~~~~~~~
main.cpp:12:14:   required from here
   12 |     std::sort(points.begin(), points.end());
      |     ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Read the first line for the actual problem (no match for 'operator<' on two Points) and the required from here line (Clang says in instantiation of ... requested here) for your line that triggered it. Everything in between is the library's plumbing. C++20 concepts (Module 12 lesson 5) exist largely to turn this wall of text into one line: constraints not satisfied.

A linker error

/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x1a): undefined reference to `compute(int)'
collect2: error: ld returned 1 exit status

No error: with a column, ld or collect2 in the text, and undefined reference (or undefined symbol from Clang's lld): the compile stage passed, and the linker cannot find a definition. Lesson 2 listed the causes — a file not on the command line, a library not linked, a declaration whose definition was never written or does not match.

Sanitizers: the checks the compiler cannot do

Undefined behaviour is invisible at compile time by design. The sanitizers add the checks at run time:

$ g++ -std=c++20 -g -O1 -fsanitize=address,undefined main.cpp -o main
$ ./main
main.cpp:7:14: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
=================================================================
==4242==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000034
READ of size 4 at 0x602000000034 thread T0
    #0 0x55d4 in main main.cpp:9

AddressSanitizer (ASan) reports out-of-bounds reads and writes, use-after-free, double free and leaks, with a stack trace naming your line. UndefinedBehaviorSanitizer (UBSan) reports signed overflow, bad shifts, null dereference and misaligned access. -g gives the reports line numbers. The program runs two to three times slower, so these are development flags, never release ones — and the judge, which compiles with plain -O2, cannot tell you why a run failed, only that it did. Module 7 lesson 6 and Module 19 use them on the memory-error catalogue.

A debugger, in outline

Build with -g -O0 (debug information, no optimisation, so variables and lines are where you expect them), then:

$ gdb ./main
(gdb) break main.cpp:9        # stop at line 9
(gdb) run < input.txt         # start, feeding the file as stdin
(gdb) next                    # execute one line, stepping over calls
(gdb) step                    # execute one line, stepping into calls
(gdb) print total             # show a variable
(gdb) backtrace               # the call stack: where am I, and how did I get here
(gdb) continue                # run to the next breakpoint

lldb (Clang's debugger) has the same commands with slightly different spellings, and every IDE wraps one of the two. The one command to know from memory is backtrace after a crash: it names the line that failed and the calls that led to it.

Pitfalls

  • Reading only the first word of the first error and guessing. Read the location, the message and the note.
  • Fixing the last error first. The first is the cause; the rest are often its consequences.
  • A warning-free build with -w. That flag suppresses all warnings; it is how bugs become invisible.
  • Debugging optimised code. At -O2 variables vanish and lines reorder; build with -O0 -g to debug.

Key takeaways

  • A diagnostic is file:line:col: severity: message, followed by the source line, a caret and notes; errors stop the build, warnings do not, notes explain.
  • Fix the first error first and read the notes; for a template error, the first line and required from here.
  • -Wall -Wextra always; -Werror to keep a codebase at zero warnings; never -w.
  • undefined reference with ld in the text is the linker, not the compiler.
  • -fsanitize=address,undefined with -g finds memory errors and UB at run time; the judge (Clang 18, -std=c++20 -O2) has no sanitizers, so test locally with them.

Common questions

What do -Wall and -Wextra do?

They are GCC and Clang flags that enable warnings: -Wall turns on the ones almost always worth fixing, such as unused variables, missing returns and signed/unsigned comparisons, and -Wextra adds a second tier. Despite its name, -Wall is not everything — -Wshadow and -Wconversion must be asked for separately.

What is the difference between GCC and Clang?

Both are C++ compilers that accept the same flags and print diagnostics in the same file:line:col shape. GCC (g++) is the Linux default and ships libstdc++; Clang (clang++) is LLVM-based, the macOS default, known for readable messages, and uses either libstdc++ or its own libc++.

What are AddressSanitizer and UndefinedBehaviorSanitizer?

They are run-time checkers enabled with -fsanitize=address,undefined. AddressSanitizer reports out-of-bounds access, use-after-free, double free and leaks; UndefinedBehaviorSanitizer reports signed overflow, bad shifts, null dereferences and misaligned access. Add -g for line numbers; programs run two to three times slower, so they are development flags, not release ones.

What does "comparison of integers of different signs" mean?

It is the -Wsign-compare warning, typically for i < v.size() with int i. The int is converted to the unsigned type that size() returns, so a negative value becomes huge and -1 < v.size() is false. Use a std::size_t index, or convert the size deliberately.

How do you debug a C++ program with gdb?

Build with -g -O0 so variables and lines match the source, then run gdb ./main. Set a breakpoint with break main.cpp:9, start with run < input.txt, step with next or step, inspect with print total, and after a crash type backtrace to see the failing line and the calls that led to it.

Mark as readSign in to keep your place.

How to Read C++ Compiler Errors: Warnings and Sanitizers

A C++ diagnostic gives file, line, column, severity and message. Which warning flags to enable, how to read template and linker errors, plus ASan and UBSan.

  • Course: C++ study plan
  • Module: C++ and the toolchain
  • Kind: Lesson
  • Reading time: 14 min
  • Runtime: Clang 18 · C++20

How do you read a C++ compiler error?

A C++ compiler error has the form file:line:col: error: message, followed by the source line, a caret under the offending token and any note: lines that give context. Fix the first error first, because the errors after it are often its consequences, and read the notes. For a template error, the first line says what failed and the required from here line names your code.

Lesson

A C++ compiler is the most thorough reviewer your code will ever have, and it works for free — if you read what it says. This lesson teaches the shape of a diagnostic so that you can read it like a stack trace: which file, which line and column, what kind of message, and what the notes underneath are pointing at. It also covers the warning flags worth turning on, what a template error and a linker error look like, and the two run-time tools — sanitizers and a debugger — that catch what the compiler cannot.

The three compilers

CompilerDriverWhereNotes
GCCg++Linux default, MinGW on WindowsShips libstdc++, the standard library most Linux C++ uses
Clangclang++macOS default (Apple Clang), Linux, WindowsLLVM-based; famously readable diagnostics; uses libstdc++ or its own libc++
MSVCclWindows, Visual StudioDifferent flag syntax (/W4, /std:c++20) and a different message format

GCC and Clang accept the same flags and produce diagnostics in the same file:line:col: kind: message shape, so what you learn on one transfers. The study judge is Clang 18 with libstdc++ 14, -std=c++20 -O2; the local gate this track was written against is GCC 14 with -Wall -Wextra. Code that is clean on either compiles on the other unless it leans on a compiler extension — variable-length arrays, __int128, #include <bits/stdc++.h> — which is why the track avoids them.

Anatomy of a compile error

#include <iostream>

int main() {
    int total = 0;
    for (int i = 0; i < 3; ++i) {
        totl += i;
    }
    std::cout << total << '\n';
}
main.cpp:6:9: error: use of undeclared identifier 'totl'; did you mean 'total'?
    6 |         totl += i;
      |         ^~~~
      |         total
main.cpp:4:9: note: 'total' declared here
    4 |     int total = 0;
      |         ^
1 error generated.

Read it left to right. main.cpp:6:9 — file, line 6, column 9; every editor can jump there. error — the severity: an error stops the build, a warning does not, a note is not a problem at all but context for the message above it. Then the message, often with a suggestion. The source line and the caret show exactly which token, and the note: says where the related declaration is. 1 error generated. is Clang's summary; GCC ends with nothing.

Two habits: fix the first error first, because one missing semicolon or unknown type cascades into dozens of nonsense errors below it; and read the notes, because for an overload or template failure the note chain (candidate function not viable: ...) is the actual explanation.

Warnings, and which flags to turn on

A warning is the compiler saying "this is legal and probably wrong". -Wall enables the ones almost always worth fixing; -Wextra adds a second tier; -Wpedantic complains about extensions the standard does not allow; -Werror turns every warning into an error, so a build with warnings fails — which is how teams keep the count at zero.

Warning (flag)What it catchesWhy it matters
unused variable (-Wunused-variable)a name declared and never readusually a typo elsewhere, or dead code
may be used uninitialised (-Wuninitialized, -Wmaybe-uninitialized)a read before any writeundefined behaviour; initialise with {}
control reaches end of non-void function (-Wreturn-type)a missing return on some pathundefined behaviour, and -O2 makes it bite
comparison of integers of different signs (-Wsign-compare)i < v.size() with int ithe int converts to unsigned; -1 < v.size() is false
unused value (-Wunused-value)x + 1;a statement with no effect
-Wshadow (not in -Wall)an inner variable hiding an outer onethe bug from lesson 3
-Wconversion (not in -Wall)int x = 3.7;, long into intsilent narrowing

Build with -Wall -Wextra from day one. A warning you have decided is harmless is noise that hides the next real one; fix it, or silence that one case deliberately, never by lowering the flags.

A template error

Template errors are long because the compiler reports the failure where it happened — deep in the library — and then the chain of instantiations that led there. Sorting a vector of a type that has no <:

/usr/include/c++/14/bits/predefined_ops.h:45:23: error: no match for 'operator<' (operand types are 'const Point' and 'const Point')
   45 |       { return *__it1 < *__it2; }
      |                ~~~~~~~^~~~~~~~
main.cpp:12:14:   required from here
   12 |     std::sort(points.begin(), points.end());
      |     ~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Read the first line for the actual problem (no match for 'operator<' on two Points) and the required from here line (Clang says in instantiation of ... requested here) for your line that triggered it. Everything in between is the library's plumbing. C++20 concepts (Module 12 lesson 5) exist largely to turn this wall of text into one line: constraints not satisfied.

A linker error

/usr/bin/ld: main.o: in function `main':
main.cpp:(.text+0x1a): undefined reference to `compute(int)'
collect2: error: ld returned 1 exit status

No error: with a column, ld or collect2 in the text, and undefined reference (or undefined symbol from Clang's lld): the compile stage passed, and the linker cannot find a definition. Lesson 2 listed the causes — a file not on the command line, a library not linked, a declaration whose definition was never written or does not match.

Sanitizers: the checks the compiler cannot do

Undefined behaviour is invisible at compile time by design. The sanitizers add the checks at run time:

$ g++ -std=c++20 -g -O1 -fsanitize=address,undefined main.cpp -o main
$ ./main
main.cpp:7:14: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
=================================================================
==4242==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000034
READ of size 4 at 0x602000000034 thread T0
    #0 0x55d4 in main main.cpp:9

AddressSanitizer (ASan) reports out-of-bounds reads and writes, use-after-free, double free and leaks, with a stack trace naming your line. UndefinedBehaviorSanitizer (UBSan) reports signed overflow, bad shifts, null dereference and misaligned access. -g gives the reports line numbers. The program runs two to three times slower, so these are development flags, never release ones — and the judge, which compiles with plain -O2, cannot tell you why a run failed, only that it did. Module 7 lesson 6 and Module 19 use them on the memory-error catalogue.

A debugger, in outline

Build with -g -O0 (debug information, no optimisation, so variables and lines are where you expect them), then:

$ gdb ./main
(gdb) break main.cpp:9        # stop at line 9
(gdb) run < input.txt         # start, feeding the file as stdin
(gdb) next                    # execute one line, stepping over calls
(gdb) step                    # execute one line, stepping into calls
(gdb) print total             # show a variable
(gdb) backtrace               # the call stack: where am I, and how did I get here
(gdb) continue                # run to the next breakpoint

lldb (Clang's debugger) has the same commands with slightly different spellings, and every IDE wraps one of the two. The one command to know from memory is backtrace after a crash: it names the line that failed and the calls that led to it.

Pitfalls

  • Reading only the first word of the first error and guessing. Read the location, the message and the note.
  • Fixing the last error first. The first is the cause; the rest are often its consequences.
  • A warning-free build with -w. That flag suppresses all warnings; it is how bugs become invisible.
  • Debugging optimised code. At -O2 variables vanish and lines reorder; build with -O0 -g to debug.

Key takeaways

  • A diagnostic is file:line:col: severity: message, followed by the source line, a caret and notes; errors stop the build, warnings do not, notes explain.
  • Fix the first error first and read the notes; for a template error, the first line and required from here.
  • -Wall -Wextra always; -Werror to keep a codebase at zero warnings; never -w.
  • undefined reference with ld in the text is the linker, not the compiler.
  • -fsanitize=address,undefined with -g finds memory errors and UB at run time; the judge (Clang 18, -std=c++20 -O2) has no sanitizers, so test locally with them.

Common questions

What do -Wall and -Wextra do?

They are GCC and Clang flags that enable warnings: -Wall turns on the ones almost always worth fixing, such as unused variables, missing returns and signed/unsigned comparisons, and -Wextra adds a second tier. Despite its name, -Wall is not everything — -Wshadow and -Wconversion must be asked for separately.

What is the difference between GCC and Clang?

Both are C++ compilers that accept the same flags and print diagnostics in the same file:line:col shape. GCC (g++) is the Linux default and ships libstdc++; Clang (clang++) is LLVM-based, the macOS default, known for readable messages, and uses either libstdc++ or its own libc++.

What are AddressSanitizer and UndefinedBehaviorSanitizer?

They are run-time checkers enabled with -fsanitize=address,undefined. AddressSanitizer reports out-of-bounds access, use-after-free, double free and leaks; UndefinedBehaviorSanitizer reports signed overflow, bad shifts, null dereferences and misaligned access. Add -g for line numbers; programs run two to three times slower, so they are development flags, not release ones.

What does "comparison of integers of different signs" mean?

It is the -Wsign-compare warning, typically for i < v.size() with int i. The int is converted to the unsigned type that size() returns, so a negative value becomes huge and -1 < v.size() is false. Use a std::size_t index, or convert the size deliberately.

How do you debug a C++ program with gdb?

Build with -g -O0 so variables and lines match the source, then run gdb ./main. Set a breakpoint with break main.cpp:9, start with run < input.txt, step with next or step, inspect with print total, and after a crash type backtrace to see the failing line and the calls that led to it.

Exercises

Diagnostic summary

Summarise a compiler's output. Read lines until the input ends. A line is a diagnostic when it contains : error: , : warning: or : note: ; everything before that marker is its location (file:line:col). Other lines (source excerpts, carets, 1 error generated.) are ignored.

Input: zero or more lines. Output: two lines: errors=<e> warnings=<w> notes=<n>, then first error: <location> for the first error line, or first error: none.

Read with while (std::getline(std::cin, line)); line.find(": error: ") returns the position of the marker or std::string::npos when it is absent, and line.substr(0, pos) is the location.

Example: input

main.cpp:12:5: error: use of undeclared identifier 'x'
main.cpp:3:10: note: 'y' declared here
main.cpp:20:9: warning: unused variable 'z' [-Wunused-variable]
util.cpp:4:1: error: expected ';' after expression

→

errors=2 warnings=1 notes=1
first error: main.cpp:12:5

Warnings as errors

Apply -Werror to a compiler's output. The first line of input is the set of flags the build used, for example -Wall -Werror or -O2 -Wall. Every following line is compiler output. When the flags contain -Werror, every : warning: marker is rewritten to : error: before the line is printed; otherwise lines are printed unchanged. Every line is echoed. After the last line print Clang's summary: <w> warning(s) and <e> error(s) generated. with correct singular/plural forms and only the non-zero parts — 1 warning generated., 2 errors generated., 1 warning and 2 errors generated. — or build ok when there were neither.

Input: the flags line, then zero or more lines of compiler output. Output: every output line (promoted when -Werror is on), then the summary line.

line.find(": warning: ") locates the marker and line.replace(pos, len, ": error: ") rewrites it in place.

Example: input

-Wall -Werror
main.cpp:4:9: warning: unused variable 'x' [-Wunused-variable]
main.cpp:9:1: error: expected ';' after expression

→

main.cpp:4:9: error: unused variable 'x' [-Wunused-variable]
main.cpp:9:1: error: expected ';' after expression
2 errors generated.

In this module: C++ and the toolchain

← Console input and output · Checkpoint — C++ and the toolchain →