FaceHost - Статьи

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

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

  • ссылка на неразрешенный внешний символ ...
  • неопределённая ссылка на символ ...
  • 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() {}