Skip to content
C++ Better Explained
Go back

How to Fix Undefined Reference Errors in C++ (Linker Errors Explained)

Edit page

How to Fix “Undefined Reference” Errors in C++ (Linker Errors Explained)

Introduction: Why “Undefined Reference” Confuses Everyone

You’ve written what seems like perfectly good C++ code. It compiles fine. Then the compiler throws this at you:

undefined reference to `main'
undefined reference to `square(int)'
undefined reference to `std::cout'

What does this even mean? You defined square() right there in your code. You’re using std::cout which is from the standard library. Why is the compiler complaining?

Here’s the frustrating part: The compiler didn’t complain. The linker complained. And that’s the key to understanding what’s happening.

Most beginners think compilation and linking are the same thing. They’re not. The compiler translates your C++ code into machine code. The linker stitches all that machine code together into a working program. “Undefined reference” errors happen during linking, and they happen for reasons the compiler never sees.

In this article, I’m going to explain exactly what the linker does, why these errors happen, and—most importantly—how to fix every type of undefined reference error.


The C++ Build Process Explained: Preprocessing → Compilation → Linking

Before we talk about linker errors, you need to understand the complete build process. C++ goes through three distinct phases:

Phase 1: Preprocessing

The preprocessor handles directives like #include and #define. It doesn’t compile anything; it just manipulates the text of your source code.

#include <iostream>    // Copy the entire iostream header here
#define PI 3.14159     // Replace PI with 3.14159 everywhere

int main() {
    cout << PI << endl;
}

After preprocessing, your source file is expanded, but still C++ code (not yet compiled).

Phase 2: Compilation

The compiler takes the preprocessed code and translates it into object files (.o files on Linux, .obj on Windows). These contain machine code, but they’re not executable yet.

g++ -c myprogram.cpp -o myprogram.o

The -c flag means “compile only, don’t link.”

Here’s the key insight: The compiler doesn’t need to know where functions are defined. It just assumes they exist somewhere.

If you call square(5) but square() is defined in a different file, the compiler doesn’t care. It generates machine code that says “call the function at address X,” but it leaves that address as a placeholder. The compiler trusts the linker to fill in the correct address later.

Phase 3: Linking

The linker takes all your object files (and any libraries you’re using) and stitches them together into an executable program. This is where “undefined reference” errors happen.

The linker needs to find the machine code for every function you called. If a function was called but never defined anywhere, the linker can’t find it, and you get an “undefined reference” error.

g++ myprogram.o -o myprogram

Or, more commonly, you do everything in one step:

g++ myprogram.cpp -o myprogram

This runs all three phases automatically.


What Does the Linker Actually Do?

The linker’s job is to resolve symbols. A symbol is a name (like a function or variable) that was referenced somewhere in your code.

Here’s what happens:

  1. The linker reads all object files and libraries
  2. For each symbol that’s used, it looks for the definition
  3. If found, it records the address
  4. If not found, it reports “undefined reference”

Think of it like filling in a phone book. If someone calls square(), you look up square in the phone book. If square is listed, great! You call that number. If not listed, you can’t make the call—that’s your “undefined reference.”


What Causes “Undefined Reference to X”? Seven Common Causes

Cause 1: Forgetting to Define a Function (Declaration vs Definition)

This is the most common error. You declare a function (promise it exists) but never define it (actually write the code).

// myprogram.cpp
void square(int x);  // Declaration: "I promise square() exists"

int main() {
    square(5);       // Use square()
    return 0;
}
// ERROR: square() was never actually defined!

To fix it, provide the definition:

// myprogram.cpp
void square(int x) {  // Definition: actual code
    cout << x * x << endl;
}

int main() {
    square(5);
    return 0;
}

The distinction:

You can declare without defining (useful when the function is in another file), but you must define somewhere.

Cause 2: Missing Source Files from Compilation

You have multiple .cpp files, but you forgot to tell the compiler about all of them:

# helper.cpp
void square(int x) {
    cout << x * x << endl;
}

# main.cpp
void square(int x);  // Declaration

int main() {
    square(5);
    return 0;
}

# Compiling incorrectly:
g++ main.cpp -o myprogram
# ERROR: undefined reference to `square(int x)'

The linker never sees helper.cpp, so it can’t find the definition.

Fix: Compile both files:

g++ main.cpp helper.cpp -o myprogram

Or:

g++ -c main.cpp -o main.o
g++ -c helper.cpp -o helper.o
g++ main.o helper.o -o myprogram

Cause 3: Missing Library Flags

Libraries contain precompiled code. To use them, you need to link against them with -l flag:

#include <iostream>
#include <cmath>

int main() {
    cout << sqrt(16) << endl;  // sqrt() is from libm
    return 0;
}

If you compile without linking the math library:

g++ myprogram.cpp -o myprogram
# ERROR: undefined reference to `sqrt'

Fix: Link against libm:

g++ myprogram.cpp -o myprogram -lm

Common libraries:

Cause 4: Template Implementation in .cpp vs .h Files

Templates are tricky. When you use a template, the compiler needs to see the implementation at the point of use. If you put the implementation in a .cpp file, the compiler won’t see it.

// math.h
template <typename T>
T square(T x);  // Declaration only

// math.cpp
template <typename T>
T square(T x) {
    return x * x;
}

// main.cpp
#include "math.h"
int main() {
    cout << square(5) << endl;
    return 0;
}
// ERROR: undefined reference (compiler never saw the implementation in math.cpp)

Fix: Put the implementation in the header file:

// math.h
template <typename T>
T square(T x) {
    return x * x;
}

// main.cpp
#include "math.h"
int main() {
    cout << square(5) << endl;  // Works!
    return 0;
}

Or use explicit template instantiation in math.cpp:

// math.cpp
template <typename T>
T square(T x) {
    return x * x;
}

// Explicitly instantiate for int and double
template int square<int>(int);
template double square<double>(double);

Cause 5: Static Member Variables Not Defined Outside the Class

If you declare a static member variable in a class, you must define it outside the class:

// BadClass.h
class MyClass {
public:
    static int count;  // Declaration
};

// main.cpp
#include "BadClass.h"
int main() {
    MyClass::count = 0;
    return 0;
}
// ERROR: undefined reference to `MyClass::count'

Fix: Define it in a .cpp file:

// MyClass.cpp
#include "MyClass.h"
int MyClass::count = 0;  // Definition

Cause 6: Mixing C and C++ (extern “C” Issue)

When you mix C and C++ code, you need to tell the compiler how to handle names. C doesn’t use name mangling (the process of encoding function arguments in the function name). C++ does.

// c_function.c
int add(int a, int b) {
    return a + b;
}

// main.cpp
int add(int a, int b);  // Declares C function, but C++ name mangling applies!

int main() {
    cout << add(5, 3) << endl;
    return 0;
}
// ERROR: undefined reference (C++ is looking for mangled name, but C has the unmangled name)

Fix: Use extern "C" to tell C++ “this is C code, don’t mangle the name”:

// main.cpp
extern "C" {
    int add(int a, int b);
}

int main() {
    cout << add(5, 3) << endl;  // Works!
    return 0;
}

Or in a header:

// add.h
#ifdef __cplusplus
extern "C" {
#endif

int add(int a, int b);

#ifdef __cplusplus
}
#endif

Cause 7: Typos in Function Names

This is embarrassing, but common. You declare a function with one name and define it with another:

void calcul8(int x);  // Declaration with typo

int main() {
    calcul8(5);
}

void calculate(int x) {  // Definition without typo
    cout << x << endl;
}
// ERROR: undefined reference to `calcul8'

The linker sees the declaration uses calcul8, but the definition is calculate. These don’t match, so you get an undefined reference.

Fix: Check spelling carefully. Use an IDE with autocomplete to avoid this.


How to Read and Understand Linker Error Messages

Linker errors can look cryptic, but they follow a pattern:

undefined reference to `foo()'
/tmp/cczZxP1P.o: In function `main':
main.cpp:(.text+0x15): undefined reference to `foo()'
collect2: error: ld returned 1 exit status

Breaking this down:

The key information is the function name: foo(). Now you know to look for why foo() wasn’t linked.


Fixing Each Type of Error With Code Examples

Example 1: Missing Function Definition

// WRONG
#include <iostream>
using namespace std;

void greet();  // Declaration only

int main() {
    greet();
    return 0;
}
// Compile: g++ myprogram.cpp
// ERROR: undefined reference to `greet()'
// CORRECT
#include <iostream>
using namespace std;

void greet() {  // Definition
    cout << "Hello!" << endl;
}

int main() {
    greet();
    return 0;
}
// Compile: g++ myprogram.cpp -o myprogram
// SUCCESS

Example 2: Multi-File Project

// math.h
#ifndef MATH_H
#define MATH_H

int add(int a, int b);
int multiply(int a, int b);

#endif

// math.cpp
#include "math.h"

int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

// main.cpp
#include <iostream>
#include "math.h"
using namespace std;

int main() {
    cout << "5 + 3 = " << add(5, 3) << endl;
    cout << "5 * 3 = " << multiply(5, 3) << endl;
    return 0;
}

// WRONG compilation:
// g++ main.cpp -o myprogram
// ERROR: undefined reference to `add' and `multiply'

// CORRECT compilation:
// g++ main.cpp math.cpp -o myprogram
// SUCCESS

Example 3: Using a Library

// program.cpp
#include <iostream>
#include <cmath>
using namespace std;

int main() {
    double result = sqrt(16);
    cout << "sqrt(16) = " << result << endl;
    return 0;
}

// WRONG:
// g++ program.cpp -o program
// ERROR: undefined reference to `sqrt'

// CORRECT (link math library):
// g++ program.cpp -o program -lm
// SUCCESS

How to Compile Correctly With g++ (Common Flags Explained)

Here are the most important g++ flags for avoiding linker errors:

# Basic compilation
g++ myprogram.cpp -o myprogram

# Compile with multiple source files
g++ main.cpp helper.cpp -o myprogram

# Compile with debug symbols (helps with error messages)
g++ -g main.cpp helper.cpp -o myprogram

# Link with libraries
g++ main.cpp -o myprogram -lm -lpthread

# Compile to object file only (no linking)
g++ -c main.cpp -o main.o

# Link object files
g++ main.o helper.o -o myprogram

# Include directories (if headers are in non-standard locations)
g++ main.cpp -o myprogram -I/usr/local/include

# Library directories (if libraries are in non-standard locations)
g++ main.cpp -o myprogram -L/usr/local/lib -lmylib

# Warnings and errors (always use these!)
g++ -Wall -Wextra main.cpp helper.cpp -o myprogram

CMake Basics for Managing Multiple Files

For larger projects, using make or CMake is much easier than typing compilation commands manually.

Here’s a simple CMakeLists.txt:

cmake_minimum_required(VERSION 3.10)
project(MyProject)

# Create an executable from these source files
add_executable(myprogram main.cpp helper.cpp)

# Link libraries
target_link_libraries(myprogram m)  # m is the math library

Then compile:

mkdir build
cd build
cmake ..
make

CMake handles all the file management and linking for you.


Prevention Tips

  1. Use a header file for each module

    // math.h - declare functions
    // math.cpp - define functions
  2. Always include headers in your source files

    #include "math.h"  // before using add()
  3. Use -Wall -Wextra flags

    g++ -Wall -Wextra main.cpp helper.cpp -o myprogram

    These flags catch many mistakes before linking.

  4. Link all source files

    g++ main.cpp helper.cpp lib.cpp -o myprogram
    # Don't compile just one file and expect everything to work
  5. Link required libraries

    g++ program.cpp -lm -lpthread  # math and threading libraries
  6. Check for typos in function names Use an IDE with autocomplete to avoid spelling mistakes.

  7. Use version control Track your files so you know if something got deleted accidentally.

  8. Understand the build process Know the difference between compilation and linking. They’re separate phases.


Conclusion: Linker Errors Are Usually Simple to Fix

“Undefined reference” errors seem mysterious, but they’re not. They happen because:

Once you understand that the compiler translates code, and the linker stitches it together, these errors make sense.

The fix is almost always one of:

  1. Add the missing function definition
  2. Compile all source files together
  3. Link the required libraries
  4. Move template code to headers

You now have the knowledge to diagnose and fix any “undefined reference” error. And remember: the error message tells you exactly which function is missing. Read it carefully, and the fix is usually obvious.


Ready to improve your C++ skills further? Check out our guide to memory management to understand how to allocate and free resources properly. Or explore the build process in more detail to master makefiles and build systems.


Take Your C++ Further

If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.

👉 Get the C++ Better Explained Ebook — $19



Edit page
Share this post on:

Previous Post
C++ STL Containers Explained: Choosing the Right Container for Every Situation
Next Post
Virtual Functions and Polymorphism in C++: How Runtime Dispatch Actually Works