При попытке сборки программы появляется сообщение об ошибке одного из следующих видов:
Что это значит, и как исправить такую ошибку?
- ссылка на неразрешенный внешний символ ...
- неопределённая ссылка на символ ...
- unresolved external symbol ...
- undefined reference to ...
- ошибка LNK2019 ...
- LNK2001: неразрешенный внешний символ ...
Что это значит, и как исправить такую ошибку?
Ответ
Определение
Данная ошибка означает, что в процессе компоновки программы, компоновщик не смог найти определение некоторой сущности, на которую есть ссылка (попытка использования) в программе.
К таким сущностям может относиться, например, функция или переменная.
Причины и решения
Возможных причин появления ошибки может быть несколько и это зависит от того, что представляет из себя собираемый проект. Всё множество ситуаций можно разбить на две большие группы:
Используются сторонние библиотеки
Не указана необходимая (статическая) библиотека для компоновщика.
Например, к проекту подключен только *.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) перед сущностью. Обычно это решается макросами. Данная ситуация чаще всего характерна именно для библиотек, находящихся в процессе разработки, и доступных для модификации самому разработчику, нежели для каких-то стабильных версий действительно внешних для проекта библиотек. После разрешения экспортирования библиотеку, конечно же, нужно пересобрать перед непосредственным использованием проблемной сущности.
Библиотека указана, но не совпадает разрядность библиотеки и компилируемого кода.
В общем случае, разрядность собираемого проекта (приложения или библиотеки) должна совпадать с разрядностью используемой сторонней библиотеки. Обычно производители библиотек предоставляют возможность выбора 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, то эти функции могут быть не загружены.
Сторонние библиотеки не используются
Просто отсутствует определение функции.
void f(int); // всего лишь объявление. Нет `тела` функции
int main(){
f(42); // undefined reference to f(int)
}
Требуется добавить определение функции f:
void f(int) {
// тело функции
}
Может быть ещё частный случай с ошибкой вида:
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 class T
struct C {
friend void f(C<T>); // объявляет *НЕ*шаблонную дружественную функцию
};
template class T // определяет шаблонную функцию
void f(C<T>) { }
int main() {
C<int> c;
f(c); // undefined reference to f(C<int>)
}
Чтобы объявить шаблонную дружественную функцию, требуется добавить указание шаблонности:
template class T
struct C {
template class V // добавили шаблонность для friend функцииfriend void f(CT);
};
Важно, что имя шаблонного параметра для дружественной функции должно отличаться от имени параметра шаблонного класса T, т.к. иначе будет ошибка о сокрытии имени. В частном случае имя класса можно вовсе не указывать, и оставить template class. Но это не всегда будет правильным решением, т.к. фактически могла потребоваться дружественная специализация шаблона функции, а не сама шаблонная функция.
Отсутствует определение статической переменной класса.
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 class T
T f(T); // объявление шаблона без реализации
// unit.cpp
#include "unit.h"
template class T
T f(T t) { return t + t; } // реализация шаблона
undefined reference to
template
int f int(int); // явное инстанцирование для int
template
double f double (double); // явное инстанцирование для double
// main.cpp
#include "unit.h"
int main() {
f(2); // ok intf(1.5); // ok doublef('a'); // undefined reference to char f<char>(char)
}
Файл с кодом не был скомпилирован.
Например, в случае использования make-файла не было прописано правило построения файла, а в случае использования IDE типа Visual Studio *.cpp файл не добавлен в список файлов проекта.
Виртуальная функция в базовом классе не объявлена как = 0 (pure-virtual).
struct B {
void virtual f();
};
struct D : B {
void f() {}
};
int main() {
D d;
}
При использовании иерархии классов функция в базовом классе, не имеющая реализации должна быть помечена как "чистая":
struct B {
void virtual 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() {}