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

1. The compiler prints a warning and nothing else. Do you have a program to run?

Yes. Exactly. Errors stop the build; warnings do not. Fix them anyway — they are usually right.

Not quite. That is the confusion this chapter exists to clear up. A warning compiled your code as written and then commented on it.

Not quite. Backwards: -Wall and -Wextra ask for *more* warnings. They do not change whether a program was produced.

2. You change one line and get twelve errors. What do you fix first?

Not quite. The last message is usually the furthest downstream of the real problem, so it is the least informative one to chase.

Yes. One mistake makes everything after it stop making sense. Fix the first and the rest often vanish, because they were never separate problems.

Not quite. It feels faster and usually is not — you end up fixing eleven things that were never broken.

3. A message says undefined reference to 'main', with no line number. Which tool is complaining?

Not quite. The compiler always shows you a line and a caret. No line number is the clue that this is something else.

Yes. Your code compiled fine. The linker then went looking for main to use as the starting point and there was not one — usually a typo in the name.

Not quite. Nothing has run yet. The failure happened while the program was being assembled, before it existed.

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.