C++ / GETTING STARTED
Compiler errors, warnings, and reading diagnostics
Read g++ diagnostics precisely: find the first real error in a cascade, decode a [-Wflag], and fix the warnings that quietly change your results.
What you will learn
- Map file:line:column, severity and [-Wflag] in a diagnostic back to the exact token
- Fix the first error first, since later ones are often parser cascade damage
- Compile with -Wall -Wextra and treat -Wsign-compare hits as real bugs
- Recognise that -Wall is not all: -Wshadow and -Wconversion are opt-in
Understanding Compiler errors, warnings, and reading diagnostics
An error and a warning answer two different questions. An error means the translation unit breaks a rule the compiler is required to diagnose, so it refuses to produce an object file and there is nothing to run. A warning means the code is legal C++ that the compiler suspects does not mean what you wrote, and it still compiles. Nothing in the standard fixes the set of warnings, which is why g++, clang++ and MSVC agree almost perfectly about errors and constantly disagree about warnings.
g++ formats a diagnostic as path:line:column: severity: message, then echoes the source line with a caret under the offending token, and may add note: lines carrying context or a suggested replacement. Trust the column: it points at the token the compiler choked on, not at the start of the statement. Because a C++ parser has to guess how to resume after a syntax mistake, one missing semicolon or unclosed brace can produce dozens of later errors on innocent lines, so you fix the first one and recompile instead of working down the list. In template and overload failures the note: chain matters more than the error text itself: the top of the chain is your call site, the bottom is inside the library.
Warnings are the cheapest static analysis in the toolchain, and they exist because C++ lets you write code whose meaning is not what it looks like. The compiler can see that you compared a signed int with an unsigned size_type, or wrote = where == was meant, but it is not allowed to reject well-formed code, so the warning is the only signal you get before the wrong branch runs. Ask for the checks explicitly: -Wall is a curated subset, -Wextra adds more, and -Wshadow, -Wconversion and -Wold-style-cast are separate flags. Once a file builds clean, -Werror keeps it that way by turning any new warning into a failed build.
<cstddef>
<iostream>
<vector>
int main() {
std::vector<int> v{10, 20, 30};
int n = -1;
// g++ -Wall warns on the next line:
// comparison of integer expressions of different signedness:
// 'int' and 'std::vector<int>::size_type' {aka 'long unsigned int'} [-Wsign-compare]
if (n < v.size()) {
std::cout << "branch: n < size\n";
} else {
std::cout << "branch: n >= size\n";
}
std::cout << "v.size() = " << v.size() << '\n';
std::cout << "n as size_type = " << static_cast<std::size_t>(n) << '\n';
}
A diagnostic is a location plus a claim: error: means the compiler could not translate that token, warning: means it could but doubts you meant it.
Worked examples
A warning that hides a dead branch
Assignment written where comparison was meant compiles and runs, and only -Wparentheses points at it.
<iostream>
int main() {
int attempts = 3;
// g++ -Wall: suggest parentheses around assignment used as truth value [-Wparentheses]
if (attempts = 0) {
std::cout << "no attempts left\n";
} else {
std::cout << "attempts left: " << attempts << '\n';
}
}
Example explained
Line 1attempts = 0 stores 0 and the whole expression evaluates to 0, which converts to false, so the if body is unreachable.
Line 2The assignment also destroyed the value 3, which is why the else branch prints 0.
Line 3-Wall enables -Wparentheses; with a bare g++ main.cpp this file compiles in total silence.
Line 4Writing if (attempts == 0) removes both the warning and the bug.
Anatomy of a g++ error message
A file that will not compile, and how each part of the diagnostic maps back to a character in the source.
<iostream>
int main() {
int total = 0;
for (int i = 1; i <= 3; ++i) {
total += i;
}
std::cout << "sum: " << total << '\n';
std::cout << Total << '\n';
}
Example explained
Line 1main.cpp:9:18 is file, line, column: column 18 is the T of Total, not the start of the statement.
Line 2error: means the translation unit is ill-formed, so no object file is written and the program cannot be run at all.
Line 3^~~~~ underlines the whole offending token, and the line beneath it is a fix-it hint naming a similar identifier that is in scope.
Line 4The loop above is correct; C++ identifiers are case sensitive, so Total and total are two unrelated names.
A useful warning that -Wall does not enable
Shadowing compiles quietly under -Wall -Wextra and needs -Wshadow to be reported.
<iostream>
int counter = 0;
void bump() {
int counter = 0;
++counter;
std::cout << "local counter: " << counter << '\n';
}
int main() {
bump();
bump();
std::cout << "global counter: " << counter << '\n';
}
Example explained
Line 1The local int counter hides the global one for the rest of bump, so ++counter touches the local object.
Line 2That local is created and zeroed on every call, which is why the second call prints 1 again instead of 2.
Line 3-Wall -Wextra say nothing here; g++ -Wshadow reports declaration of 'counter' shadows a global declaration.
Important notes
Wording, column numbers and fix-it hints differ between compilers and versions; the flag name in brackets and the line number are the parts you can rely on when searching or reporting.
undefined reference to 'foo' is a linker message, not a compiler diagnostic: every file compiled successfully, so it carries no line, no caret and no -W flag that could silence it.
Common mistakes
Starting from the last error: a missing semicolon after a class definition on line 12 can spray thirty errors over lines 40 and beyond, and you end up rewriting code that was never broken.
Dismissing warnings because the program still runs: if (attempts = 0) builds and prints output, and the -Wparentheses warning is the only sign the if body is unreachable.
Assuming -Wall means every warning, then losing an afternoon to a shadowed variable or a silent narrowing conversion that -Wshadow or -Wconversion would have flagged instantly.
Try it yourself
Change, predict, then run
In a browser editor, print every element of std::vector<int>{4, 8, 15} using for (int i = 0; i < v.size(); ++i) and compile with -Wall -Wextra. Then change i to std::size_t and confirm the warning is gone while the printed output is identical.
Open the C++ workspaceCheck your understanding
g++ prints 18 errors: the first on line 12, the rest between lines 40 and 63. Why is fixing line 12 first the right move?
- g++ sorts diagnostics by severity, so the first one listed is always the most serious.
- The root cause is reported last, so line 63 is the real problem and line 12 is a symptom of it.
- A missing token on line 12 can leave the parser mis-synchronised, so many of the later errors are artefacts of it.
- Errors after the first one describe runtime behaviour rather than compilation, so they are not build problems.
Show answer
After a syntax mistake the parser must guess where a valid construct resumes, and a wrong guess makes correct code further down look broken; fixing line 12 and recompiling is how you learn which of the other 17 errors were real. The severity-ordering option is tempting because the first error usually is the one that matters, but g++ emits diagnostics in source order and has no severity ranking among errors, so the first one is first by position, not by importance.