10 C++ Compile and Link Errors Beginners Hit First, and What Each Message Means
Key takeaways
What the ten errors beginners see most in C++ actually mean, including undefined reference, segfaults, redefinition and no matching function: which build stage produces each one, the exact GCC and Clang wording, and how to fix it.
Introduction: “I don’t understand what the error message means”
The most frustrating moment when first learning C++ is a screen full of error messages that seem to be about something else. Most of that confusion disappears once you know which stage of the build produced the message. A C++ build has three stages, and each produces a recognizable family of errors:
- Compiling each
.cppfile separately: messages start withfile.cpp:LINE:COL: error:. Syntax, types, names. - Linking the compiled object files into a program: messages mention
ld,collect2orLNKand usually have no useful line number. Missing or duplicated definitions. - Running the program: no compiler message at all, just a crash or wrong output.
This article covers ten errors beginners hit first, in roughly the order people meet them. For each one it shows the GCC wording (and Clang’s where it differs), what causes it, and how to fix it. Examples were checked against GCC 11 and Clang 14; newer versions word some messages slightly differently but keep the same key phrases.
The article is organized by error message: you have a message on screen and want to know what it means. If you would rather learn the handful of habits that cause most beginner errors in the first place (a missing semicolon after a class, = instead of ==, off-by-one loops, returning the address of a local), see 15 Common C++ Beginner Mistakes, which is organized by mistake instead.
undefined reference to ‘function’
Cause
This is a linker error. The compiler saw a declaration (void myFunction();), trusted it, and left a note for the linker saying “find myFunction somewhere”. The linker then looked in every object file and library it was given and found no definition.
Error Message
/usr/bin/ld: /tmp/ccXyZ.o: in function `main':
main.cpp:(.text+0x5): undefined reference to `myFunction()'
collect2: error: ld returned 1 exit status
The collect2: error: ld returned 1 exit status line is only a summary. The useful part is the line above it, which names the missing symbol and the function that used it. MSVC reports the same problem as LNK2019: unresolved external symbol.
Common Situations
- The definition is in a
.cppfile that was not compiled or linked. With several files,g++ main.cpp -o programbuilds onlymain.cpp. In an IDE or CMake project, the file was never added to the target. - The definition’s signature does not match the declaration.
void myFunction(int)declared,void myFunction(long)defined: to the linker, those are two different functions. - A template function is defined in a
.cppfile. - A missing library. Using
pthread_create,sqrtfrom some C libraries, or a third-party library without-lpthread,-lmor-lfooon the link line. With GCC, libraries must come after the files that use them. - A virtual function was declared but never defined, which produces the stranger-looking
undefined reference to 'vtable for MyClass'. The fix is to define every non-pure virtual function, most often the destructor.
Solution
// ❌ Wrong: Only declaration, no definition
// header.h
void myFunction();
// main.cpp
#include "header.h"
int main() {
myFunction(); // undefined reference!
}
// ✅ Correct: Add definition
// header.h
void myFunction();
// impl.cpp
#include "header.h"
void myFunction() {
// implementation
}
// Include all .cpp files during compilation
// g++ main.cpp impl.cpp -o program
Including impl.cpp from main.cpp with #include "impl.cpp" also makes the error go away, and beginners find this fix often. Avoid it: as soon as impl.cpp is also compiled on its own, which every IDE and build system does, you get the opposite error, multiple definition of 'myFunction()'.
For Template Functions
// ❌ Wrong: Template defined in .cpp
// header.h
template<typename T>
void process(T value);
// impl.cpp
template<typename T>
void process(T value) {
// implementation
}
// ✅ Correct: Template defined in header
// header.h
template<typename T>
void process(T value) {
// implementation (write in header)
}
A template is not a function yet; it is a recipe the compiler uses to generate process<int>, process<double> and so on, and it generates them only in files where it can see the full definition. main.cpp sees only the declaration, so it asks the linker for void process<int>(int), and impl.cpp never generated it because nothing in that file used process<int>. The error message shows the instantiated name, which is the clue that this is the template case. Besides moving the definition to the header, you can keep it in the .cpp file and add explicit instantiations there (template void process<int>(int);), which works when the set of types is known in advance.
segmentation fault (core dumped)
Cause
Unlike the other nine, this is a runtime error: the program compiled, started, and then the operating system killed it for touching memory it may not access. Typical causes are dereferencing a null pointer, following a pointer to freed memory, running far past the end of an array, and stack overflow from infinite recursion.
Error Message
Segmentation fault (core dumped)
That is the whole message. There is no line number, which is why the debugging tools below matter.
Common Situations and Fixes
The usual causes are dereferencing a null pointer, reading past the end of an array, using memory after delete, and unbounded recursion. Only the null dereference crashes reliably. An out-of-bounds write or a use-after-free is undefined behavior: the memory usually still belongs to your program, so the write succeeds, corrupts a neighbouring variable or the allocator’s bookkeeping, and the crash (if any) comes later somewhere else. That is why a program can “work” in one run and crash in the next. Containers with .at(), smart pointers instead of new/delete, and checking pointers that may legitimately be null remove most of these cases. Each cause, with code that reproduces it and the fix, is in C++ Segmentation Fault: Five Causes.
Debugging Tip
# Debug with gdb
g++ -g program.cpp -o program
gdb ./program
(gdb) run
# Check crash point
(gdb) backtrace
The most useful single flag for this class of bugs is AddressSanitizer: g++ -g -fsanitize=address program.cpp. It stops the program at the exact line of the invalid access, even when it would not have crashed, and prints messages such as heap-use-after-free or stack-buffer-overflow together with where the memory was allocated and freed. See C++ Segmentation Fault: Five Causes and Debugging with GDB for more.
error: redefinition of ‘class/function’
Cause
The compiler saw the same class or function defined twice in one translation unit (one .cpp file after all its #includes are pasted in). The usual reason is a header without an include guard that is included twice, directly or through another header.
Error Message
error: redefinition of 'class MyClass'
error: redefinition of 'void myFunction()'
note: 'void myFunction()' previously defined here
The note: line points to the first definition, which is often in a different header from the one where the error is reported.
Common Situations
- No include guard in header file
- Function definition in header (when not inline), which produces a different error, shown below
Solution
// ❌ No include guard
// header.h
class MyClass {
// ...
};
// ✅ Use include guard
// header.h
#ifndef HEADER_H
#define HEADER_H
class MyClass {
// ...
};
#endif
// Or use #pragma once (simpler)
// header.h
#pragma once
class MyClass {
// ...
};
#pragma once is not in the C++ standard, but GCC, Clang and MSVC all support it, and it avoids the classic include-guard mistake of copying a header and forgetting to rename its macro, after which the second header is silently skipped.
For Function Definitions
// ❌ Function definition in header (duplicates when included in multiple .cpp)
// header.h
void myFunction() {
// implementation
}
// ✅ Only declaration in header, definition in .cpp
// header.h
void myFunction();
// impl.cpp
void myFunction() {
// implementation
}
// Or use inline
// header.h
inline void myFunction() {
// implementation
}
This case is easy to confuse with the one above, and the include guard does not fix it. Each .cpp file that includes the header compiles its own copy of myFunction, which is legal inside each file. The conflict appears only when the linker combines them: multiple definition of 'myFunction()'; first defined here. inline tells the linker that identical copies are expected and one may be kept. The same applies to global variables defined in headers (int counter = 0;), where the fix is inline int counter = 0; (C++17) or extern in the header plus one definition in a .cpp. Fixing “multiple definition” Linker Errors covers this in detail.
error: no matching function for call
Cause
A function is called with arguments that no overload can accept: the wrong type, the wrong number of arguments, or a const object passed where a non-const reference is required.
Error Message
error: no matching function for call to 'process(std::string&)'
note: candidate: 'void process(double)'
note: no known conversion for argument 1 from 'std::string' to 'double'
Read the note: lines. The compiler lists every candidate it considered and, for each one, the argument that did not fit. With many overloads (for example, std::cout << something), the list can be long, and the reason for the one you intended is somewhere in the middle.
Common Situations
- Argument type mismatch
- Argument count mismatch
- const mismatch
Solution
// ❌ Argument type mismatch
void process(double value) {
// ...
}
int main() {
std::string str = "hello";
process(str); // error: no matching function!
}
// ✅ Match types
void process(const std::string& value) {
// ...
}
int main() {
std::string str = "hello";
process(str); // OK
}
// ❌ const mismatch
void print(std::string& str) {
// ...
}
int main() {
const std::string str = "hello";
print(str); // error: binding reference of type 'std::string&' to 'const std::string' discards qualifiers
}
// ✅ Add const
void print(const std::string& str) {
// ...
}
The const case also hits temporaries: print("hello") or print(a + b) fails against std::string&, because a non-const reference cannot bind to a temporary. Making a parameter const T& whenever the function does not modify it avoids both problems.
error: ‘identifier’ was not declared in this scope
Cause
A name is used where the compiler cannot find any declaration for it: the header was not included, the name is in a namespace, the name is misspelled, or it is used before the line that declares it.
Error Message
error: 'myVariable' was not declared in this scope
error: 'vector' was not declared in this scope; did you mean 'std::vector'?
Clang says use of undeclared identifier 'myVariable'. Recent GCC and Clang versions add a did you mean suggestion, which is right more often than not.
Common Situations
- Header file not included
- Namespace not specified
- Using variable before declaration
Solution
// ❌ Header not included
int main() {
std::cout << "Hello\n"; // error: 'cout' is not a member of 'std'
}
// ✅ Include header
#include <iostream>
int main() {
std::cout << "Hello\n"; // OK
}
// ❌ Namespace not specified
#include <vector>
int main() {
vector<int> vec; // error: 'vector' was not declared!
}
// ✅ Add std:: or using namespace
#include <vector>
int main() {
std::vector<int> vec; // OK
}
// ❌ Using variable before declaration
int main() {
x = 10; // error: 'x' was not declared!
int x;
}
// ✅ Use after declaration
int main() {
int x;
x = 10; // OK
}
A related trap: code that compiles on one compiler and fails on another with this error, typically for std::string, std::size_t or std::setw. Standard headers include each other in unspecified ways, so <iostream> happens to bring in <string> on one implementation and not on another. Include the header for every standard name you use, even when it compiles without it.
warning: implicit conversion loses integer precision
Cause
A value of a wider type is stored in a narrower one, so large values would be cut off. This is a warning, not an error: the program compiles, and the conversion happens silently.
Error Message
warning: implicit conversion loses integer precision: 'long' to 'int' [-Wshorten-64-to-32] (Clang)
warning: conversion from 'long int' to 'int' may change value [-Wconversion] (GCC)
The first wording is Clang’s, which Xcode enables by default. GCC only warns with -Wconversion, which is not part of -Wall -Wextra. What -Wall does warn about by default is the closely related comparison of integer expressions of different signedness: 'int' and 'std::vector<int>::size_type' [-Wsign-compare], typically from for (int i = 0; i < vec.size(); ++i).
Common Situations
- Large type → small type conversion
- size_t → int conversion
Solution
// ❌ Implicit conversion (warning)
long bigNumber = 1000000000L;
int smallNumber = bigNumber; // warning!
// ✅ Explicit casting
long bigNumber = 1000000000L;
int smallNumber = static_cast<int>(bigNumber);
// ❌ size_t → int conversion
std::vector<int> vec = {1, 2, 3};
int size = vec.size(); // warning!
// ✅ Use size_t or explicit casting
std::vector<int> vec = {1, 2, 3};
size_t size = vec.size(); // OK
// Or
int size = static_cast<int>(vec.size());
static_cast silences the warning, but it does not make a large value fit; it only documents that you checked. Before adding a cast, decide whether the value can actually exceed the target range. If it can, use a wider type. The signed/unsigned mix has its own trap: vec.size() - 1 on an empty vector is not -1 but the largest size_t value, so a loop like for (size_t i = 0; i < vec.size() - 1; ++i) runs through memory it should not touch. A range-based for avoids the index arithmetic entirely.
error: use of deleted function
Cause
The code tries to use a function that was explicitly deleted with = delete, or implicitly deleted by the compiler. For beginners, this is almost always an attempt to copy an object that can only be moved, such as std::unique_ptr, std::thread or std::ifstream.
Error Message
error: use of deleted function 'MyClass::MyClass(const MyClass&)'
note: 'MyClass::MyClass(const MyClass&)' is implicitly deleted because the default definition would be ill-formed
The (const MyClass&) parameter identifies it as the copy constructor. When the note says “implicitly deleted”, look at the class’s members: one of them cannot be copied, so the whole class cannot be copied either.
Common Situations
- Copying unique_ptr
- Class with copy constructor = delete
- Passing a move-only object by value, or
push_backof an lvalue into astd::vector<std::unique_ptr<T>>
Solution
// ❌ Copying unique_ptr
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = ptr1; // error: use of deleted function!
// ✅ Use move
std::unique_ptr<int> ptr1 = std::make_unique<int>(10);
std::unique_ptr<int> ptr2 = std::move(ptr1); // OK
// ❌ Class with deleted copy constructor
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
};
NonCopyable obj1;
NonCopyable obj2 = obj1; // error: use of deleted function!
// ✅ Use reference or pointer
NonCopyable obj1;
NonCopyable& obj2 = obj1; // OK (reference)
After std::move(ptr1), ptr1 is empty (nullptr), and dereferencing it is the null-pointer crash from section 2. std::move does not move anything by itself; it marks the object as something that may be moved from, and the move constructor of ptr2 does the transfer.
error: expected ’;’ …
Cause
A syntax error, most often a missing semicolon. The compiler reports it at the first token that cannot continue the statement, which is usually on the next line, not the line that is missing the semicolon.
Error Message
error: expected ';' after class definition
error: expected ',' or ';' before 'int'
Common Situations
- Missing semicolon at end of class definition
- Missing semicolon at end of variable declaration
Solution
// ❌ Missing semicolon at end of class definition
class MyClass {
int value;
} // error: expected ';' after class definition
// ✅ Add semicolon
class MyClass {
int value;
}; // OK
// ❌ Missing semicolon at end of variable declaration
int main() {
int x = 10
int y = 20; // error: expected ',' or ';' before 'int' (reported on this line)
}
// ✅ Add semicolon
int main() {
int x = 10;
int y = 20; // OK
}
A missing ; after a class in a header is the worst version of this, because the error appears in whichever file includes the header next, often on a line that looks perfectly fine, such as int main(). When an error makes no sense on the line it points to, look at the end of the previous declaration, including the last lines of the most recently included header.
error: invalid use of incomplete type
Cause
A class was only forward-declared (class MyClass;) and then used in a way that needs its full definition: creating an object, calling a member function, calling sizeof, or inheriting from it.
Error Message
error: invalid use of incomplete type 'class MyClass'
note: forward declaration of 'class MyClass'
error: aggregate 'MyClass obj' has incomplete type and cannot be defined
Common Situations
- Only forward declaration, header not included
- Circular dependency
Solution
// ❌ Only forward declaration, then use
class MyClass; // forward declaration
int main() {
MyClass obj; // error: invalid use of incomplete type!
obj.doSomething();
}
// ✅ Include header
#include "MyClass.h"
int main() {
MyClass obj; // OK
obj.doSomething();
}
// Solving circular dependency
// A.h
#pragma once
class B; // forward declaration
class A {
B* b; // pointer is OK
};
// B.h
#pragma once
class A; // forward declaration
class B {
A* a; // pointer is OK
};
A forward declaration is enough whenever the compiler does not need to know the class’s size or members: pointers, references, and function declarations that take or return the type. It is not enough for a member of that type by value, for calling any member function, or for delete through the pointer. In the circular case, the member functions of A that use b->something() belong in A.cpp, which includes B.h. Putting them inline in A.h brings the error back.
error: ‘class Y’ has no member named ‘X’
Cause
The code accesses a member that does not exist in that class (usually a typo, including capitalization), or one that exists but is not accessible from here.
Error Message
error: 'class MyClass' has no member named 'getvalue'; did you mean 'getValue'? (GCC)
error: no member named 'getvalue' in 'MyClass' (Clang)
error: 'int MyClass::value' is private within this context
Common Situations
- Typo
- Accessing private member (a different message, shown above)
- Using
.on a pointer instead of->, which GCC reports asrequest for member 'getValue' in 'ptr', which is of pointer type 'MyClass*' (maybe you meant to use '->' ?)
Solution
// ❌ Typo
class MyClass {
public:
int getValue() const { return value; }
private:
int value;
};
int main() {
MyClass obj;
obj.getvalue(); // error: no member named 'getvalue'! (typo)
}
// ✅ Correct name
int main() {
MyClass obj;
obj.getValue(); // OK
}
// ❌ Accessing private member
class MyClass {
private:
int value;
};
int main() {
MyClass obj;
obj.value = 10; // error: 'value' is private!
}
// ✅ Use public getter/setter
class MyClass {
public:
void setValue(int v) { value = v; }
int getValue() const { return value; }
private:
int value;
};
int main() {
MyClass obj;
obj.setValue(10); // OK
}
Members declared inside class are private by default, while members of a struct are public by default; that is the only difference between the two keywords. A class where everything was written without an access specifier therefore produces the “is private” error on every member access.
How to Read Error Messages
Error Message Structure
filename:line:column: error: error type: detailed description
Example
main.cpp:10:5: error: 'x' was not declared in this scope
x = 10;
^
How to read:
- Check filename and line number:
main.cpp:10→ line 10 of main.cpp - Check error type:
'x' was not declared in this scope→ x not declared - Check code:
x = 10;→ using x without declaration
Messages that begin with In file included from ... or In instantiation of ... are context lines, not separate errors: they describe how the compiler got to the line where the actual error: is. Template errors in particular print many context lines before the one that matters; How to Read C++ Template Error Messages walks through one.
When Multiple Errors Occur in Chain
Solve from the first error. A missing brace or semicolon can make the compiler misunderstand everything that follows, so one mistake often produces dozens of messages. Fix the first one, recompile, and look again. Link errors appear only once compilation succeeds, so a new wave of undefined reference messages after fixing compile errors is progress, not a regression.
I still follow this rule strictly, because the opposite habit, scrolling to the bottom of the output and fixing whatever is there, is the most common way beginners spend an afternoon on an error that disappears by itself once the real one is fixed.
Debugging Strategies
Enable Compiler Warnings
# Enable all warnings
g++ -Wall -Wextra -Werror main.cpp -o program
# -Wall: basic warnings
# -Wextra: additional warnings
# -Werror: treat warnings as errors
Warnings catch many of the runtime bugs above before the program runs: using an uninitialized variable, if (x = 5) instead of ==, a function that forgets to return, a signed/unsigned comparison. Adding -Werror from the first day of a project is far easier than cleaning up hundreds of warnings later.
Include Debug Symbols
# Include debug information
g++ -g main.cpp -o program
# Debug with gdb
gdb ./program
Step-by-Step Debugging
- Solve in order: compile errors → link errors → runtime errors
- Solve one error at a time, recompiling after each fix
- Search for the key phrase of the message (for example
"crosses initialization"), not the whole line with your file names
Simplify Code
When an error does not make sense, cut the program down to the smallest version that still produces it. The act of removing unrelated code usually reveals the cause, and if it does not, the small example is what someone else needs to help you.
A routine for any compile error
- Identify the stage:
file:line: error:is the compiler,undefined reference/multiple definition/ld returned 1is the linker, a crash without a message is runtime. - Solve from the first error, then recompile.
- Enable warnings (
-Wall -Wextra) and treat them as errors in new projects. - Use AddressSanitizer for crashes and out-of-bounds bugs.
- Headers: include guards against
redefinition,inlineor a.cppdefinition againstmultiple definition.
Related Articles
- LNK2019 Unresolved External Symbol in C++: Five Causes
- Fixing “multiple definition” Linker Errors in C++
- C++ Segmentation Fault: Five Causes and Debugging with GDB
- 15 Common C++ Beginner Mistakes: From Compile Errors to Runtime Bugs
- How to Read C++ Template Error Messages
- C++ nullptr
- C++ Classes and Objects
- C++ Smart Pointers
- C++ Debugging Basics: gdb and valgrind
Frequently Asked Questions (FAQ)
Q. The compiler printed dozens of errors. Which one should I fix first?
A. Fix the first error in the output and recompile, because many of the later messages are usually side effects of that first one. A missing semicolon, an unclosed brace, or a missing #include can make the compiler misread everything after it, so dozens of errors may disappear with a single fix. With GCC you can use -fmax-errors=1 (Clang: -ferror-limit=1) to see only the first one. Link errors such as undefined reference come from a later stage, so they only appear once the code compiles cleanly.