Read compiler, linker and runtime errors the way experienced C++ developers do, fix the 15 you will hit most, and catch memory bugs with sanitizers and a debugger.
Tell a compile error, a link error, a runtime exception and undefined behaviour apart, and know which tool finds each
Read a clang diagnostic from the first error down, and find your own line inside a template error
Fix the 15 errors every C++ beginner meets, from a missing semicolon to a use-after-free
Catch standard exceptions and print what() to see what went wrong
Turn on warnings and AddressSanitizer, and step through a program with lldb or gdb
01
Compile, link, run: where each error comes from
✓
A C++ program can fail at four different moments, and the error looks completely different at each. Knowing which stage you are in is half the fix.
The last row is the dangerous one. Undefined behaviour may crash, may print garbage, or may appear to work and fail later on another machine. Sanitizers (below) turn it into a clear report.
Stage
Who reports it
Looks like
Typical cause
Compile
The compiler (clang, gcc, MSVC)
main.cpp:4:19: error: …
Syntax, types, names, const, templates
Link
The linker (ld)
Undefined symbols / undefined reference to
Declared but not defined, missing library or source file
Run: exception
The C++ runtime
terminating due to uncaught exception of type std::out_of_range
A thrown exception nobody caught
Run: crash or garbage
The operating system, or nobody
segmentation fault, exit 139, or wrong output
Undefined behaviour: null or dangling pointers, out-of-bounds reads
C++main.cpp
12345678910111213
#include<iostream>// A declaration tells the compiler a function exists; the definition gives// the linker its body. Both must exist somewhere in the program.doubletax(double amount);intmain(){
std::cout <<tax(100.0)<<'\n';}doubletax(double amount){return amount *0.18;}
Outputcompiled & run with real C++
18
The call in main compiles because of the declaration at the top; the program links because the definition exists below. Delete the definition and you get error 5 in this module: a link error, not a compile error.
02
Reading a compiler diagnostic
✓
A clang diagnostic has a fixed shape: file:line:column: error: message, the offending line with a caret ^ under the exact spot, and sometimes a fix-it suggestion on the line below. Follow-up note: lines explain it: where something was declared, which overloads were tried and why each was rejected.
1
Fix the first error first
One mistake (a missing ; or }) can produce dozens of follow-on errors. Fix the first one, recompile, and many of the rest disappear.
2
Read the message, then the caret
The column number and caret usually point at the exact token. "expected ';'" with the caret after 40 means the semicolon belongs there.
3
In template errors, find your own file
Errors inside the standard library print pages of .../c++/v1/... paths. Scan for the note that says in instantiation of … requested here with main.cpp in it: that is the line you wrote.
4
Read the notes on "no matching function"
Each candidate function not viable note says exactly which argument failed and why ("no known conversion from 'std::string' to 'int' for 1st argument").
5
Turn warnings on and treat them seriously
Compile with -Wall -Wextra -Wpedantic. Many runtime bugs (uninitialised variables, sign comparisons, missing returns) are announced as warnings first.
bash
123456
# the flags worth using on every build while learning
c++ -std=c++20 -Wall -Wextra -Wpedantic -g -fsanitize=address,undefined main.cpp -o prog
./prog
# release build: optimise, keep warnings, drop sanitizers
c++ -std=c++20 -Wall -Wextra -O2 main.cpp -o prog
03
Compile errors: syntax, names and types
✓
The most common compile errors, each compiled for real. The messages are from clang; gcc and MSVC word them differently but point at the same line.
Error you will hit
1. expected ';' at end of declaration
C++
123456
#include<iostream>intmain(){int total =40
std::cout << total <<'\n';}
main.cpp:4:19: error: expected ';' at end of declaration
4 | int total = 40
| ^
| ;
1 error generated.
Why the compiler said that
Every statement ends with a semicolon. Line breaks mean nothing to the compiler, so it reports the error at the end of line 4, where the semicolon should be, and even shows the fix-it under the caret.
The fix
Add the semicolon where the caret points. If the reported line looks fine, check the end of the line above it, and for a missing ; after a struct or class closing brace.
C++
123456
#include<iostream>intmain(){int total =40;
std::cout << total <<'\n';}
Error you will hit
2. use of undeclared identifier 'cout'
C++
12345
#include<iostream>intmain(){
cout <<"hello\n";}
main.cpp:4:5: error: use of undeclared identifier 'cout'; did you mean 'std::cout'?
4 | cout << "hello\n";
| ^~~~
| std::cout
.../c++/v1/iostream:57:42: note: 'std::cout' declared here
57 | extern _LIBCPP_EXPORTED_FROM_ABI ostream cout;
| ^
1 error generated.
Why the compiler said that
Everything in the standard library lives in the std namespace. The same error appears for a typo in a variable name, or for a name whose header was never included.
The fix
Write std::cout. Avoid using namespace std; at file scope in real code: it pulls hundreds of names into your scope and causes clashes. If the name really is undeclared, add the right #include.
main.cpp:8:18: error: no matching function for call to 'area'
8 | std::cout << area(w, 5) << '\n';
| ^~~~
main.cpp:4:5: note: candidate function not viable: no known conversion from 'std::string' (aka 'basic_string<char>') to 'int' for 1st argument
4 | int area(int width, int height) { return width * height; }
| ^ ~~~~~~~~~
1 error generated.
Why the compiler said that
C++ never converts a string to a number implicitly. The note names the argument (1st) and both types, which is everything you need.
The fix
Convert explicitly with std::stoi (and handle bad input, see error 13), or pass the right type in the first place.
main.cpp:3:14: error: cannot assign to variable 'maxUsers' with const-qualified type 'const int'
3 | maxUsers = 200;
| ~~~~~~~~ ^
main.cpp:2:15: note: variable 'maxUsers' declared const here
2 | const int maxUsers = 100;
| ~~~~~~~~~~^~~~~~~~~~~~~~
1 error generated.
Why the compiler said that
const is a promise that the value never changes after initialisation, and the compiler enforces it. The same error appears when a const member function tries to modify a member, or when you write through a const& parameter.
The fix
Decide which is true: if the value must change, drop const; if it must not, the assignment is the bug.
6. invalid operands to binary expression (printing a struct)
C++
12345678
#include<iostream>structPoint{int x;int y;};intmain(){Point p{1,2};
std::cout << p <<'\n';}
main.cpp:7:15: error: invalid operands to binary expression ('ostream' (aka 'basic_ostream<char>') and 'Point')
7 | std::cout << p << '\n';
| ~~~~~~~~~ ^ ~
.../c++/v1/__ostream/basic_ostream.h:349:55: note: candidate function template not viable: no known conversion from 'Point' to 'char' for 2nd argument
.../c++/v1/__ostream/basic_ostream.h:376:53: note: candidate function template not viable: no known conversion from 'Point' to 'char' for 2nd argument
... (dozens more candidates)
Why the compiler said that
std::cout knows how to print built-in types and strings, not your types. The compiler lists every operator<< it tried; the first line is the one that matters.
The fix
Write an operator<< for your type (Module 07), or print the members yourself.
C++
123456789101112
#include<iostream>structPoint{int x;int y;};
std::ostream&operator<<(std::ostream& os,constPoint& p){return os <<'('<< p.x <<", "<< p.y <<')';}intmain(){Point p{1,2};
std::cout << p <<'\n';}
Error you will hit
7. type 'double' cannot be narrowed to 'int' in initializer list
main.cpp:3:15: error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
3 | int cents{price * 100};
| ^~~~~~~~~~~
main.cpp:3:15: note: insert an explicit cast to silence this issue
3 | int cents{price * 100};
| ^~~~~~~~~~~
| static_cast<int>( )
1 error generated.
Why the compiler said that
Brace initialisation forbids narrowing conversions that can lose information (double to int drops the fraction). That is a feature: int cents = price * 100; would compile silently and truncate 998.999… to 998.
The fix
Decide how to convert and say so. For money, round rather than truncate, or better, store amounts as integer cents from the start.
main.cpp:7:11: error: variable type 'Shape' is an abstract class
7 | Shape s;
| ^
main.cpp:2:20: note: unimplemented pure virtual method 'area' in 'Shape'
2 | virtual double area() const = 0;
| ^
1 error generated.
Why the compiler said that
A class with a pure virtual function (= 0) is an interface: it cannot be created on its own. The same error appears for a derived class that forgot to override one of the pure virtual functions; the note says which one.
The fix
Create a concrete derived class that implements every pure virtual function, and use the base only through references or pointers (Module 08).
C++
123456789101112131415
structShape{virtualdoublearea()const=0;virtual~Shape()=default;};structSquare:Shape{double side =2;doublearea()constoverride{return side * side;}};intmain(){Square s;constShape& shape = s;returnstatic_cast<int>(shape.area());}
Error you will hit
9. call to implicitly-deleted copy constructor of unique_ptr
C++
12345678
#include<memory>#include<vector>intmain(){auto p = std::make_unique<int>(42);
std::unique_ptr<int> copy = p;return*copy;}
main.cpp:6:26: error: call to implicitly-deleted copy constructor of 'std::unique_ptr<int>'
6 | std::unique_ptr<int> copy = p;
| ^ ~
.../c++/v1/__memory/unique_ptr.h:209:55: note: copy constructor is implicitly deleted because 'unique_ptr<int>' has a user-declared move constructor
209 | _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX23 unique_ptr(unique_ptr&& __u) _NOEXCEPT
| ^
1 error generated.
Why the compiler said that
A unique_ptr is the only owner of its object, so it cannot be copied; two owners would both delete it. The same error appears when you push a unique_ptr into a vector by copy, or pass it by value without moving.
The fix
Transfer ownership with std::move (the source becomes empty), pass it by reference if the callee only uses it, or switch to std::shared_ptr if shared ownership is genuinely needed.
C++
12345678
#include<memory>#include<vector>intmain(){auto p = std::make_unique<int>(42);
std::unique_ptr<int> owner = std::move(p);// p is now emptyreturn*owner;}
04
Link errors: undefined symbols
✓
The compiler turns each .cpp file into an object file separately, trusting every declaration it sees. The linker then joins the object files and libraries, and must find exactly one definition for every function actually called. When it cannot, there is no line number: the error names the missing symbol and the object file that needed it.
Error you will hit
10. Undefined symbols / undefined reference
C++
1234567
#include<iostream>doubletax(double amount);// declared, never definedintmain(){
std::cout <<tax(100.0)<<'\n';}
Undefined symbols for architecture arm64:
"tax(double)", referenced from:
_main in main-11dd9a.o
ld: symbol(s) not found for architecture arm64
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
Why the compiler said that
The declaration promised a tax(double) function, so compilation succeeded, but no object file provides its body. On Linux, gcc and ld phrase it as undefined reference to 'tax(double)'.
The fix
Define the function, or add the .cpp file that defines it to the build (in CMake: to add_executable or the library target). For a third-party function, link the library (-lname or target_link_libraries). Check the signature matches exactly: tax(float) is a different symbol.
The standard library reports some errors by throwing exceptions derived from std::exception. An exception nobody catches calls std::terminate, which prints the exception type and its what() message and aborts the program (exit code 134, SIGABRT).
libc++abi: terminating due to uncaught exception of type std::out_of_range: vector
Why the compiler said that
The vector has indexes 0 to 2. at() checks the index and throws. The same mistake with scores[3] is not checked: it is undefined behaviour that may print garbage or crash.
The fix
Loop with range-for or up to size(), check the index before using it, or catch the exception where you can respond sensibly.
C++
123456789
#include<iostream>#include<vector>intmain(){
std::vector<int> scores{90,85,77};
std::size_t i =3;if(i < scores.size()) std::cout << scores.at(i)<<'\n';else std::cout <<"no score at index "<< i <<'\n';}
libc++abi: terminating due to uncaught exception of type std::invalid_argument: stoi: no conversion
Why the compiler said that
std::stoi throws std::invalid_argument when the text does not start with a number, and std::out_of_range when the number does not fit in an int. User input and file data hit this constantly.
The fix
Catch both exceptions around the conversion, or use std::from_chars, which reports errors through a return code instead of throwing.
C++
1234567891011
#include<charconv>#include<iostream>#include<string>intmain(){
std::string qty ="two";int value =0;auto[ptr, ec]= std::from_chars(qty.data(), qty.data()+ qty.size(), value);if(ec == std::errc{}) std::cout << value <<'\n';else std::cout <<"not a number: "<< qty <<'\n';}
Catching by const reference to the most specific type you can handle, with std::exception as the last resort, lets the program report the problem and carry on:
qty 3
error: bad quantity 'two'
too big: stoi: out of range
out_of_range: vector
parseQty translates a low-level exception into one with context (which input was bad), a habit that makes production logs readable. The what() texts come from libc++; libstdc++ on Linux words them differently.
06
Memory errors and AddressSanitizer
✓
The worst C++ bugs produce no error at all: reading through a null, dangling or out-of-bounds pointer is undefined behaviour, and the program may crash, print garbage, or seem fine. AddressSanitizer (-fsanitize=address, in clang and gcc, and MSVC as /fsanitize=address) instruments every memory access and stops at the first bad one with a report naming the line. Add -g so reports show file and line numbers. Use it on every debug build and test run.
Error you will hit
13. Segmentation fault: dereferencing a null pointer
C++
12345678
#include<iostream>structNode{int value;Node* next;};intmain(){Node* head =nullptr;
std::cout << head->value <<'\n';}
$ ./prog # plain build: the program just dies, exit code 139
$ c++ -std=c++20 -g -fsanitize=address main.cpp -o prog && ./prog
AddressSanitizer:DEADLYSIGNAL
=================================================================
==91030==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x0001023986d8 bp 0x00016da66630 sp 0x00016da66610 T0)
==91030==The signal is caused by a READ memory access.
==91030==Hint: address points to the zero page.
#0 0x0001023986d8 in main main.cpp:7
#1 0x0001870084e0 in start+0x1b4c (dyld:arm64e+0x204e0)
...
SUMMARY: AddressSanitizer: SEGV main.cpp:7 in main
==91030==ABORTING
Why the compiler said that
Reading head->value reads memory at address 0, which the OS never maps, so the process receives SIGSEGV. Without the sanitizer you get no message at all, only exit code 139 (the shell may print "segmentation fault"). The sanitizer's stack (#0 … main.cpp:7) names the line.
The fix
Check pointers that can be null before using them, and prefer references (which cannot be null) or std::optional in interfaces.
$ ./prog # plain build: prints garbage and exits 0
4
$ c++ -std=c++20 -g -fsanitize=address main.cpp -o prog && ./prog
=================================================================
==91124==ERROR: AddressSanitizer: heap-use-after-free on address 0x6020000000f0 at pc 0x000104ab475c bp 0x00016b34a5c0 sp 0x00016b34a5b8
READ of size 4 at 0x6020000000f0 thread T0
#0 0x000104ab4758 in main main.cpp:6
freed by thread T0 here:
#0 0x000105398558 in _ZdlPv+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x50558)
#1 0x000104ab4704 in main main.cpp:5
previously allocated by thread T0 here:
#0 0x000105398150 in _Znwm+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x50150)
#1 0x000104ab468c in main main.cpp:4
SUMMARY: AddressSanitizer: heap-use-after-free main.cpp:6 in main
Why the compiler said that
After delete, the memory belongs to the allocator again; reading it is undefined behaviour. The plain build printed 4, not 500, and exited successfully, which is exactly why these bugs survive into production. ASan reports all three places: the bad read (line 6), the free (line 5) and the allocation (line 4).
The fix
Do not manage memory with raw new/delete. Use a plain value, or std::unique_ptr, whose object lives exactly as long as its owner (Module 06).
C++
1234567
#include<iostream>#include<memory>intmain(){auto balance = std::make_unique<int>(500);
std::cout <<*balance <<'\n';}// freed automatically here, after the last use
Error you will hit
15. Dangling reference after vector::push_back
C++
123456789
#include<iostream>#include<vector>intmain(){
std::vector<int> v{1,2,3};int& first = v[0];
v.push_back(4);
std::cout << first <<'\n';}
$ ./prog # plain build: prints garbage (should be 1)
4
$ c++ -std=c++20 -g -fsanitize=address main.cpp -o prog && ./prog
=================================================================
==91178==ERROR: AddressSanitizer: heap-use-after-free on address 0x6020000000f0 at pc 0x000100cf4b20 bp 0x00016f10a450 sp 0x00016f10a448
READ of size 4 at 0x6020000000f0 thread T0
#0 0x000100cf4b1c in main main.cpp:8
0x6020000000f0 is located 0 bytes inside of 12-byte region [0x6020000000f0,0x6020000000fc)
freed by thread T0 here:
#0 0x0001016b0558 in _ZdlPv+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x50558)
...
#10 0x000100cf4dd8 in std::__1::vector<int, std::__1::allocator<int>>::push_back[abi:nqe220106](int&&) vector.h:466
#11 0x000100cf4ab8 in main main.cpp:7
Why the compiler said that
The vector was full, so push_back allocated a bigger buffer, moved the elements and freed the old 12-byte buffer. first still refers into the freed buffer. The same applies to pointers and iterators into a vector: any operation that can reallocate invalidates them.
The fix
Take the reference after the last insertion, keep an index instead of a reference, or reserve() enough capacity up front if you must keep references stable.
Out-of-bounds, use-after-free, double free, leaks (on Linux)
-fsanitize=address -g
UndefinedBehaviorSanitizer
Signed overflow, bad shifts, misaligned or null access, invalid enum values
-fsanitize=undefined
ThreadSanitizer
Data races between threads
-fsanitize=thread (not together with ASan)
Debugger (lldb, gdb, Visual Studio)
Anything: step line by line and inspect state
Build with -g -O0, set breakpoints
Static analysis
Bugs and style issues without running the code
clang-tidy, cppcheck, IDE inspections
Valgrind (Linux)
Memory errors and leaks in an unmodified binary
valgrind ./prog
bash
1234567891011
$ c++ -std=c++20 -g -O0 main.cpp -o prog
$ lldb ./prog
(lldb) breakpoint set --file main.cpp --line 12 # or: b main.cpp:12
(lldb) run # runs until the breakpoint
(lldb) frame variable # show every local variable
(lldb) print total # show one expression
(lldb) next # run the current line, step over calls
(lldb) step # step into the call on this line
(lldb) bt # backtrace: who called whom
(lldb) continue
(lldb) quit
A minimal lldb session (gdb uses almost the same commands: run, break, next, step, print, bt).
For quick investigations, print to std::cerr (unbuffered, and kept separate from real output), and check assumptions with assert, which aborts with the file and line when a condition is false in debug builds and disappears when compiled with -DNDEBUG.
C++main.cpp
123456789101112131415
#include<cassert>#include<iostream>#include<vector>doubleaverage(const std::vector<int>& xs){assert(!xs.empty()&&"average of an empty vector");longlong sum =0;for(int x : xs) sum += x;
std::cerr <<"[debug] sum="<< sum <<" n="<< xs.size()<<'\n';// diagnostics to stderrreturnstatic_cast<double>(sum)/ xs.size();}intmain(){
std::cout <<average({3,4,8})<<'\n';}
Outputcompiled & run with real C++
5
Only stdout (5) is the program's real output; the [debug] line goes to stderr, so redirecting output to a file or a pipe keeps it clean. Call average({}) in a debug build and the assert stops the program with the message and line number.
08
Index of the 15 errors
✓
#
Message (clang)
Stage
Usual fix
1
expected ';' at end of declaration
Compile
Add the semicolon at the caret
2
use of undeclared identifier 'cout'
Compile
std:: prefix, or the missing #include
3
no matching function for call to …
Compile
Read the notes: which argument, which types
4
cannot assign to variable … with const-qualified type
Compile
Drop const, or do not assign
5
no member named … in …
Compile
Typo, or wrong type; check the class
6
invalid operands to binary expression
Compile
Define the operator (e.g. operator<<)
7
cannot be narrowed … in initializer list
Compile
Explicit conversion, round deliberately
8
variable type … is an abstract class
Compile
Instantiate a concrete derived class
9
call to implicitly-deleted copy constructor of 'std::unique_ptr'
Compile
std::move, or pass by reference
10
Undefined symbols / undefined reference to
Link
Define it, add the source file or library
11
uncaught exception of type std::out_of_range
Run
Check bounds; catch where you can recover
12
uncaught exception of type std::invalid_argument: stoi
Run
Validate input; std::from_chars
13
SEGV on unknown address 0x000000000000
Run (UB)
Check for null; prefer references
14
heap-use-after-free
Run (UB)
Smart pointers instead of new/delete
15
heap-use-after-free after push_back
Run (UB)
Do not hold references across reallocation
Diagnostic
A compiler message: file:line:column, severity (error, warning, note), message, and a caret under the problem.
Linker
The tool that joins object files and libraries into a program and resolves every function call to exactly one definition.
Undefined behaviour (UB)
Code the language gives no meaning to (null or dangling access, out-of-bounds reads, signed overflow); anything may happen.
std::terminate
Called when an exception is not caught; prints the exception and aborts the program.
AddressSanitizer
A compiler instrumentation (-fsanitize=address) that detects invalid memory accesses at the moment they happen.
Backtrace
The chain of function calls that led to the current point, shown by a debugger (bt) or a sanitizer report.
Quick check
A program compiles, but building it fails with Undefined symbols for architecture arm64: "tax(double)". What is wrong?
Compilation only needs the declaration; linking needs the definition. Define the function, add the source file that defines it to the build, or link the library that provides it. Two definitions would give a "duplicate symbol" error instead.
Quick check
A program that reads a deleted pointer prints a plausible number and exits with code 0. What is the best way to find the bug?
Use-after-free is undefined behaviour, which often looks like it works. AddressSanitizer turns it into a precise report with three stacks: the bad access, the free and the allocation.
What is the difference between a compile error and a linker error in C++?
A compile error means one source file is not valid C++: a syntax mistake, a type mismatch or an unknown name, reported with a file, line and column. A linker error happens after every file compiled: a declared function or variable has no definition in any object file or library, so the linker reports an undefined symbol without a line number.
How do I debug a segmentation fault in C++?
Rebuild with -g -fsanitize=address and run the program again: AddressSanitizer reports the invalid access and the line that caused it. Alternatively run it under a debugger (lldb or gdb) and type bt after the crash to see the backtrace. Common causes are null pointers, dangling pointers or references, and out-of-bounds indexes.
Why does my C++ program work on my machine but crash elsewhere?
Usually undefined behaviour: reading uninitialised memory, a dangling reference, an out-of-bounds index or signed overflow. It can appear to work by chance with one compiler, optimisation level or memory layout and fail with another. Warnings (-Wall -Wextra) and sanitizers find most of these bugs.
Finish the C++ handbook, then get hired
Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.