Ссылка на неразрешенный внешний символ (возможные причины)

1,00
р.
При попытке сборки программы появляется сообщение об ошибке одного из следующих видов:

ссылка на неразрешенный внешний символ ... неопределённая ссылка на символ ... unresolved external symbol ... undefined reference to ... ошибка LNK2019 ... LNK2001: неразрешенный внешний символ ...

Что это значит, и как исправить такую ошибку?

Ответ
Определение
Данная ошибка означает, что в процессе компоновки программы, компоновщик не смог найти определение некоторой сущности, на которую есть ссылка (попытка использования) в программе.
К таким сущностям может относиться, например, функция или переменная.

Причины и решения
Возможных причин появления ошибки может быть несколько и это зависит от того, что представляет из себя собираемый проект. Всё множество ситуаций можно разбить на две большие группы:

1. Используются сторонние или собственные библиотеки
Не указана необходимая (статическая) библиотека для компоновщика.
Например, к проекту подключен только *.h файл с объявлениями, но отсутствует код реализации, обычно это *.lib или *.a файлы (в зависимости от используемой системы). Требуется явно подключить библиотеку к проекту.
Для Visual C++ это можно сделать добавлением следующей строки прямо в код: #pragma comment(lib, "libname.lib")
Для gcc/clang требуется указать файл через ключ -l (эль)
Для Qt в .pro файле нужно использовать переменную LIBS: LIBS += -L[путь_к_библиотеке] -l[имя_библиотеки]
Для системы сборки CMake есть target_link_libraries.

Библиотека указана, но необходимая сущность, например, класс или функция фактически не экспортируется из библиотеки.
Под Windows это может возникать из-за отсуствия __declspec(dllexport) перед сущностью. Обычно это решается макросами. Важно, чтобы признак экспортируемости был доступен в той же единице трансляции, где определена экспортируемая сущность. Например, если __declspec(dllexport) указано для функции в заголовочном файле, но этот файл не включили (через #include) в модуль, где определена функция, то эта функция не будет экспортирована. Данная ситуация чаще всего характерна именно для библиотек, находящихся в процессе разработки, и доступных для модификации самому разработчику, нежели для каких-то стабильных версий действительно внешних для проекта библиотек. После разрешения экспортирования библиотеку, конечно же, нужно пересобрать перед непосредственным использованием проблемной сущности.
Библиотека указана, но не совпадает разрядность библиотеки и компилируемого кода.
В общем случае, разрядность собираемого проекта (приложения или библиотеки) должна совпадать с разрядностью используемой сторонней библиотеки. Обычно производители библиотек предоставляют возможность выбора 32 или 64 бит версию использовать. Если библиотека поставляется в исходных кодах и собирается отдельно от текущего проекта, нужно также выбрать правильную разрядность.
Библиотека указана, но она собрана для другой (не совместимой) ОС.
Например при сборке проекта в Windows осуществляется попытка использовать бинарный файл, собранный для Linux. В данном случае нужно использовать файлы, подходящие для вашей ОС.
Библиотека указана, но она собрана другим компилятором, не совместимым с используемым.
Объектные файлы, полученные путем сборки C++ кода разными компиляторами для одной и той же ОС могут быть бинарно несовместимы друг с другом. Требуется использовать совместимые (скорее всего и вовсе одинаковые) компиляторы.
Библиотека указана, и собрана тем же компилятором, что и основной проект, но используются разные версии Run-Time библиотек.
Например, для Visual C++ возможна ситуация, когда библиотека собрана с ключом /MDd, а основной проект с /MTd. Требуется задать ключи так, чтобы использовались одинаковые версии Run-Time библиотек.
Библиотека указана, но нарушен порядок, в котором указаны зависимости.
Например, для GCC компоновщика зависимая сущность должна быть указана раньше (левее):
foo.o -lz bar.o // просматривает библиотеку 'z' после foo.o, но перед bar.o
Если bar.o ссылается на функции в z, то эти функции могут быть не загружены.

2. Сторонние или собственные библиотеки не используются
Просто отсутствует определение функции.
void f(int) // всего лишь объявление. Нет `тела` функции
int main(){ f(42) // undefined reference to `f(int)' }
Требуется добавить определение функции f:
void f(int) { // тело функции }
Иногда определение функции генерируется сторонними утилитами, в частности когда в проекте задействованы какие-то расширения синтаксиса языка. В этом случае надо убедиться, что нужные вспомогательные шаги выполняются. Например, для Qt это Meta-Object Compiler (moc), который генерит определения функций при использовании механизма сигналов/слотов. И чтобы он вызывался для вашего кода, требуется указать вспомогательный макрос Q_OBJECT.
Может быть ещё частный случай с ошибкой вида:
undefined reference to `vtable for <имя_класса>`

Такая ошибка возникает, если объявленная виртуальная функция класса, не являющаяся чистой (=0), не содержит реализации.
class C { virtual void f(int) }
Нужно такую реализацию добавить. Если это делается вне класса, надо не забыть указать имя проблемного класса, иначе это будет просто свободная функция, которая не устраняет указанную проблему:
void C::f(int) { // виртуальная функция-член вне определения класса // тело функции }
void f(int) { // свободная функция, не устраняющая проблему // тело функции }
Аналогичная ситуация может возникать при использовании пространств имён, когда объявлении функции находится в пространстве имён:
// В заголовочном файле namespace N { void f(int) }
а при реализации указать это пространство имён забыли:
// В файле реализации void f(int) { // функция в глобальном пространстве имён, не устраняющая проблему // тело функции }
namespace N { void f(int) { // функция в нужном пространстве имён // тело функции } } // конец пространства имён
Стоит заметить, что C++ разрешает перегрузку функций (существование одноимённых функций, но с разным набором параметров), и в этом случае важно, чтобы сигнатуры функций при объявлении и определении совпадали. Например, вместо объявленной void f(int) была реализована другая:
void f(const char*) { // const char* вместо int // тело функции }
При вызове f(42) будет ошибка:
undefined reference to `f(int)'

Наличие связки шаблонного класса и дружественной функции также может приводить к ошибке. Например:
template struct C { friend void f(C) // объявляет *НЕ*шаблонную дружественную функцию }
template // определяет шаблонную функцию void f(C) { }
int main() { C c f(c) // undefined reference to `f(C)' }
Чтобы объявить шаблонную дружественную функцию, требуется добавить указание шаблонности:
template struct C { template // добавили шаблонность для friend функции friend void f(C) }
Важно, что имя шаблонного параметра для дружественной функции должно отличаться от имени параметра шаблонного класса T, т.к. иначе будет ошибка о сокрытии имени. В частном случае имя класса можно вовсе не указывать, и оставить template . Но это не всегда будет правильным решением, т.к. фактически могла потребоваться дружественная специализация шаблона функции, а не сама шаблонная функция. Подробнее об использовании дружественных функций в шаблонном классе можно ознакомиться здесь.
Отсутствует определение статической переменной класса.
struct S { static int i }
int main() { S s s.i = 42 // undefined reference to `S::i' }
Нужно добавить определение (выделить память) переменной:
int S::i
Неправильная реализация шаблонного кода.
Например, реализация шаблонного кода помещена в *.cpp файл, хотя она должна находиться полностью в подключаемом *.h файле. Это требуется по той причине, что компилятор обрабатывает каждый модуль независимо, и в момент инстанцирования шаблона (подстановки конкретного типа) код его реализации должен быть виден. При этом если возможные типы шаблона известны заранее, можно произвести инстанцирование сразу рядом с телом шаблона и не предоставлять его наружу в исходном коде заголовочного файла. Пример:
// unit.h #pragma once
template T f(T) // объявление шаблона без реализации
// unit.cpp #include "unit.h"
template T f(T t) { return t + t } // реализация шаблона
template int f(int) // явное инстанцирование для int
template double f(double) // явное инстанцирование для double
// main.cpp #include "unit.h"
int main() { f(2) // ok int f(1.5) // ok double f('a') // undefined reference to `char f(char)' }
Файл с кодом не был скомпилирован.
Например, в случае использования make-файла не было прописано правило построения файла, а в случае использования IDE типа Visual Studio *.cpp файл не добавлен в список файлов проекта.
Виртуальная функция в базовом классе не объявлена как = 0 (pure-virtual).
struct B { virtual void f() }
struct D : B { void f() {} }
int main() { D d }
При использовании иерархии классов функция в базовом классе, не имеющая реализации должна быть помечена как "чистая":
struct B { virtual void f() = 0 }
Имя не имеет внешнего связывания.
Например, есть объявление функции f в модуле А и даже ее реализация в модуле B, но реализация помечена как static:
// A.cpp void f() int main() { f() // undefined reference to `f()' }
// B.cpp static void f() {}
Аналогичная ситуация может возникнуть при использовании безымянного пространства имен:
// B.cpp namespace { void f() {} }
Или даже при наличии inline у функции:
// B.cpp inline void f() {}