Chapter 2
Your First Error Message
How to read what the compiler is telling you.
You met your first error message in chapter 0, on purpose, by removing a semicolon. Good. Here’s the part nobody tells you: you’ll see thousands more, and almost every one of them is trying to help.
That’s a hard sell when you’re staring at forty lines of red. So let’s get you to the point where you can glance at one of these and know, in about two seconds, what kind of problem you have.
Errors and warnings are different animals
This distinction matters more than anything else in the chapter.
An error means the compiler gave up. No program was produced. There’s nothing to run.
A warning means you got a program, and the compiler thinks you may not have wanted it. It compiled your code as written and is raising an eyebrow.
Here’s a warning. This program builds and runs:
#include <iostream>
int main() {
"Hello, world\n";
std::cout << "Hi\n";
}
g-nouse.cpp:4:5: warning: expression result unused [-Wunused-value]
4 | "Hello, world\n";
| ^~~~~~~~~~~~~~~~
1 warning generated.
That line is a valid statement. It’s a piece of text, sitting there, being a
piece of text, and then being thrown away. Perfectly legal. Utterly pointless.
The compiler builds it and mentions the pointlessness, which is exactly what you
want from -Wall -Wextra.
Treat warnings as errors you haven’t been bitten by yet. When one appears, fix it.
The shape of a message
Take the semicolon out of hello.cpp again:
hello.cpp:4:34: error: expected ';' after expression
4 | std::cout << "Hello, world\n"
| ^
| ;
1 error generated.
Every message has the same anatomy, and once you can see it you can skim these instead of reading them:
| Piece | What it is |
|---|---|
hello.cpp |
Which file. Matters more once you have several. |
4 |
Which line. |
34 |
Which column — how far along the line. |
error: |
Severity. This or warning: or note:. |
expected ';' after expression |
What went wrong, in the compiler’s own words. |
The quoted line and ^ |
Your code, with a caret under the exact spot. |
Notice that this compiler didn’t just complain — it printed the ; it wanted,
right where it wanted it. Modern compilers suggest fixes surprisingly often, and
the suggestion is usually correct. Take it.
Your compiler has an accent
Same mistakes, different phrasing. Mac readers get the first column, Windows and Linux readers the second:
| clang says | GNU g++ says |
|---|---|
expected ';' after expression |
expected ';' before '}' token |
use of undeclared identifier 'cout' |
'cout' was not declared in this scope |
'iostrem' file not found |
iostrem: No such file or directory |
Undefined symbols: _main |
undefined reference to 'main' |
Different words, same complaint, same anatomy. And both are trying to be helpful — g++ will often follow that second message with a note telling you which header you forgot.
One mistake makes several messages
Delete the closing quote from your text:
d-quote.cpp:4:18: warning: missing terminating '"' character [-Winvalid-pp-token]
4 | std::cout << "Hello, world\n;
| ^
d-quote.cpp:4:18: error: expected expression
1 warning and 1 error generated.
One missing character, two messages, and they’re different severities. That’s normal. A single mistake can easily produce a dozen complaints, because everything after it stops making sense too.
Which leads to the most useful habit in this chapter:
Read the first message. Fix that one thing. Compile again.
Not the last, not the scariest-looking, not all of them. The first. Very often the rest evaporate, because they were never really separate problems.
Some messages come in pairs on purpose. Leave off the final } and you get:
e-brace.cpp:4:35: error: expected '}'
4 | std::cout << "Hello, world\n";
| ^
e-brace.cpp:3:12: note: to match this '{'
3 | int main() {
| ^
The second one says note:, not error:. It isn’t a second problem — it’s the
compiler showing you the opening brace it’s still waiting to close. Notes are
help. Read them.
When the message points at a file you’ve never seen
Write cout without the std:: in front, and this happens:
c-nostd.cpp:4:5: error: use of undeclared identifier 'cout'; did you mean 'std::cout'?
4 | cout << "Hello, world\n";
| ^~~~
| std::cout
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/iostream:57:42: note: 'std::cout' declared here
57 | extern _LIBCPP_EXPORTED_FROM_ABI ostream cout;
The first three lines are about your code and they even offer the fix. Then suddenly there’s a path into the depths of your machine and a line of code you’ve never seen, full of capital letters and underscores.
You haven’t broken the standard library. That note: is the compiler saying “the
real cout is over here, for reference”. It’s an FYI, not an accusation. Your bug
is still on your line, and the fix is the one it suggested.
This is worth internalising early, because when your code eventually uses more of the library, error messages start quoting library internals at length. Your mistake is nearly always in the file you were editing.
The error with no line number
Now a different creature entirely. Misspell main:
int mian() {
mian.cpp:5:1: warning: non-void function does not return a value [-Wreturn-type]
5 | }
| ^
1 warning generated.
Undefined symbols for architecture arm64:
"_main", referenced from:
<initial-undefines>
ld: symbol(s) not found for architecture arm64
clang++: error: linker command failed with exit code 1
No line number. No caret. No quoted code. Just a complaint about a missing
_main from something called ld.
That’s because your code compiled fine. Every line of it is valid C++. The
failure came afterwards, from a second program that runs once the compiling is
done and whose job is to assemble the finished thing. It went looking for the
starting point — main — and there wasn’t one.
There’s a bonus in that output, and it’s worth a moment.
Chapter 1 claimed main is special: the one function allowed to end without
reporting a number, because C++ supplies the zero for you. Look at the warning
above. The moment the function was called mian instead of main, it became an
ordinary function — and the compiler immediately complained that it doesn’t return
a value.
You just watched the compiler confirm chapter 1’s story. You don’t have to take my word for these things; you can usually get the compiler to tell you.
How to look one up
You will search for error messages for the rest of your career. There’s a knack to it: strip out everything specific to you.
Don’t paste /Users/you/cpp/hello.cpp:4:34: error: expected ';' after expression.
Your filename and your line number are the only parts of that sentence nobody else
in the world shares.
Search c++ error: expected ';' after expression instead. Keep the words the
compiler chose, drop your paths, drop your line numbers, and add c++ so you
don’t land in another language’s problems.
Exercise 1 · Cause a warning on purpose
Get your compiler to warn you without erroring. Add a line to hello.cpp that
is a piece of text and nothing else — "just sitting here"; — then compile.
You should get a warning, and you should also get a working program. Run it and confirm both. This is the difference between “the compiler is worried” and “the compiler gave up”, and feeling it once is worth more than reading it twice.
A field guide
The six you’ll meet this week. Wording varies by compiler; the meaning doesn’t.
| The message, roughly | What it actually means |
|---|---|
expected ';' |
Missing semicolon. Check the end of the line above the caret. |
use of undeclared identifier 'std' |
You forgot #include <iostream>. |
use of undeclared identifier 'cout' |
You wrote cout without std:: in front. |
'iostrem' file not found |
You misspelled a header name. |
expected '}' plus a note: to match this '{' |
An opening brace was never closed. |
undefined reference to 'main' or Undefined symbols: _main |
main is missing or misspelled. Not a compiler error — a linker error. |
Check yourself
Project
Fix the broken card
Roughly 30 minutes
Here is a fortune card with four mistakes planted in it. Type it out exactly as
written — including the mistakes — save it as card.cpp, and get it working.
#include <iostrem>
int main() {
std::cout << "+----------------------+\n";
std::cout << "| Your fortune: |\n"
cout << "| Things will improve. |\n";
std::cout << "+----------------------+\n;
}The rules: fix one thing, then recompile. Every time. Don’t try to spot all four at once, even when you can see them — the habit is the thing being trained here, and it’s the habit that will save you on a program you don’t understand at a glance.
Something satisfying happens on the first compile: you get exactly one message,
and it’s about the very first line. A bad #include stops the compiler before
it reads anything else, so the other three mistakes are invisible. Fix that one,
recompile, and two more appear at once.
Keep a note of each message as you go. Four fixes, four compiles, and you’ll have met most of this chapter’s field guide in your own terminal.
Then the last step, which is the real point. Once it compiles with no errors and no warnings, run it and look at the card.
It’s crooked. One line is a character too wide, and no compiler on earth will ever tell you, because nothing is wrong with your C++ — the program does exactly what you asked. This is the difference between code that builds and code that’s right, and it’s the whole reason chapter 11 exists.
Straighten it. Then your card is done.