Foundations · Module 1 · Lesson 6 of 9
Reading Compiler Errors and Warnings
Learn the structure of a compiler diagnostic, why one typo can print several errors, which message to fix first, and which warning flags catch the mistakes from earlier lessons.
By the end you can
- Pick out the file, line, severity and message in a compiler diagnostic
- Explain why later errors can be consequences of the first one, and fix errors in the right order
- Locate the real cause of an error that the compiler reports on the following line
- Turn on the warnings that catch the mistakes from earlier lessons
Builds on From Source Text to a Running Program.
01 · A question first
One typo, and the compiler prints a screenful of red text. Which line should you read, which can you safely ignore for now, and why do some messages point at a place where nothing is wrong?
Make a guess, then step through the trace below to check it.
02 · Watch it run
#include <iostream> int main() { itn total = 5; std::cout << total << "\n";}- message 0--
- message 1--
We compile this file. The two cells will hold the messages the compiler prints, in order. There is exactly one mistake: itn on line 4 should be int.
Machine work: compile 1: parsing from the top, nothing reported yet
03 · The idea
Start with the problem
You make one typo and the compiler answers with a screenful of text. For a beginner it reads as noise, and the instinct is to scroll to the bottom or give up. But compiler output is a structured report, and once you can read its structure it becomes the fastest feedback you will ever get. Here is what writing itn instead of int typically produces with GCC:
main.cpp: In function 'int main()':
main.cpp:4:5: error: 'itn' was not declared in this scope; did you mean 'int'?
4 | itn total = 5;
| ^~~
| int
main.cpp:5:18: error: 'total' was not declared in this scope
5 | std::cout << total << "\n";
| ^~~~~Two errors for one mistake. This lesson is about telling which message matters, where the compiler is really pointing, and which warnings to switch on so that the mistakes from earlier lessons are caught before the program ever runs.
How the idea works
The exact wording differs between compilers and versions, but the structure does not.
Anatomy of a diagnostic. Each message starts with a location: file, line and column, as in the example:4:5. Next comes the severity. An error means the program is invalid and no output file will be produced. A warning means the code is valid but probably not what you meant, and the build still succeeds. A note adds context to the message before it, such as where a name was declared or which functions were tried. Then comes the text of the complaint, usually with a source excerpt and a caret under the spot, and sometimes a suggestion. Warnings end with the name of the flag that produced them in brackets, such as [-Wunused-variable], which tells you how to control that warning.
Which stage is speaking. Lesson 1 divided the build into stages, and the message tells you which one failed. A missing file is the preprocessor, a rule broken in your text is the compiler, and undefined reference is the linker. Most early errors come from the compiler stage.
Reading order. Read the first error, go to its line, look at the caret, and then look one line above. Fix that single problem and recompile, as the last step of the trace does. Do not fix several unrelated errors at once, and do not start from the bottom of the list.
A few messages and their usual causes:
was not declared in this scope: a typo, a missing#include, a missingstd::, or a variable used outside the block that declares it.expected ';' before ...: a missing semicolon, almost always on the line above.no match for 'operator<<': you tried to insert a type that the stream does not know how to print.invalid conversion from ... to ...: a value of one type given where another is required, as in lesson 4.no matching function for call to ...: a function called with the wrong number or types of arguments. The notes below it list the candidates the compiler tried.
Warnings you want. By default compilers stay quiet about code that is legal but suspect. Ask them to speak:
g++ -std=c++20 -Wall -Wextra -g main.cpp -o mainThe flag -std=c++20 picks the language version, -Wall and -Wextra enable the common warnings, and -g adds the debug information that the last lesson of this module uses. With them, the assignment inside if (a = 5) from lesson 3, an uninitialized variable, and the signed-versus-unsigned comparison from lesson 4 are all reported before you run anything. On MSVC the equivalent is /W4.
Why it is correct
The compiler reads the example once, from top to bottom, and can only report where it noticed a contradiction, which is not always where you made the mistake. A missing semicolon is invisible until the next token fails to fit, so the report lands on the next line. To avoid giving up after the first problem, the compiler recovers: it guesses a repair and carries on, which lets it find more of your errors in a single run. The price is that later messages rest on that guess, and they can be echoes, as in the trace, where one failed declaration made every use of total fail too. Reading the first error first works because everything after it was computed with the first error already in place.
Warnings are cheap to offer for a similar reason. The compiler already analyzes how values flow through your function in order to generate and optimize code, so reporting a variable that is read before it is set costs little extra. Some of that analysis only runs when optimization is on, which is why certain warnings, such as the possibly uninitialized variable, appear only if you also compile with -O2.
What it costs
Reading time. Most errors take seconds once you know the pattern. The expensive ones are template errors, which can run to hundreds of lines because the compiler prints the whole chain of instantiations. Read the first error line, then the line that says "required from here", which points into your own code. C++20 concepts, covered at the advanced level, were designed to make these messages shorter.
Compile time. Warnings are almost free. Turning on -Wall -Wextra does not measurably slow a build.
The cost of ignoring them. A warning you silence by habit today becomes a run-time bug later, when finding it takes a debugger and an hour instead of a glance and a second. A codebase that tolerates hundreds of warnings also hides the one new warning that matters.
The -Werror flag. It turns every warning into an error. That keeps a codebase clean, but a newer compiler version adds new warnings and can suddenly break an old build, so use it in your own CI with a pinned compiler version rather than in code that other people build on their own machines.
Pitfalls and variations
Fixing the wrong end. Starting from the last error, or fixing everything in one pass, means you edit around problems that were never real. One fix, one recompile.
Trusting the line number too much. The location is where the compiler noticed, not always where the mistake is. For a missing semicolon, look at the end of the previous line. For a missing closing brace, the error may appear many lines later, at the start of the next function.
Silencing instead of understanding. Adding a cast to make a warning vanish hides the problem and removes the compiler's help. If the warning was right, the bug is still there.
Reading only the message. The caret and the notes carry the details. A note: candidate: list after a failed call shows exactly which versions of a function the compiler considered and why each was rejected.
Different compilers, different words. Clang words things differently and often gives clearer suggestions, and MSVC prints codes such as C2065 for an undeclared identifier. When a message is baffling, paste the code into Compiler Explorer and try a second compiler.
Variations. The GCC option -fmax-errors=1 stops after the first error. IDEs show the same diagnostics as underlines, produced by the compiler or by a language server built on it. The tool clang-tidy, introduced in a later module, extends the same idea with deeper checks.
Where it is used
Every professional C++ build runs with warnings on, and CI systems typically fail a pull request on any new warning. Reviewers expect code to compile cleanly. Later lessons rely on this skill: template and concept errors, ownership mistakes caught by warnings, and static analysis all arrive as diagnostics in this same format. Reading the first error first and keeping the warning list empty is one of the cheapest ways to write better C++.
The next lesson, "Stepping Through a Program With a Debugger", handles the opposite case: the program compiles cleanly and still does the wrong thing.
04 · Prove it
Question 1 of 4 · answer from memory
A compiler prints `the example:12:9: warning: unused variable 'tmp' [-Wunused-variable]`. What does this tell you?
05 · Practice
Asked in interviews
Something wrong with this lesson? Report a problem