62

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

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

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

αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119

5 Answers5

103

Определение

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

К таким сущностям может относиться, например, функция или переменная.


Причины и решения

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


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) {
    // тело функции
}

Может быть ещё частный случай с ошибкой вида:

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(C<T>);
};

Важно, что имя шаблонного параметра для дружественной функции должно отличаться от имени параметра шаблонного класса 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; } // реализация шаблона

template
int f<int>(int);           // явное инстанцирование для int

template
double f<double>(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>(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() {}
αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
  • Разве разные версии рантайма могут быть причиной такой ошибки? По-идее, должны оба рантайма слинковаться... – Pavel Mayorov Oct 14 '16 at 13:10
  • @PavelMayorov Все модули, передаваемые компоновщику при конкретном вызове, должны компилироваться с одним и тем же параметром компилятора, указывающим библиотеку времени выполнения (/MD, /MT, /LD). отсюда – αλεχολυτ Oct 14 '16 at 13:28
  • Я в курсе. Но точно ли именно такая ошибка возникает, если это требование нарушить? – Pavel Mayorov Oct 14 '16 at 13:30
  • @PavelMayorov например вот ответ на enSO. – αλεχολυτ Oct 14 '16 at 13:36
  • ...в котором буржуйским по белому написано, что при несовпадении ключей получается варнинг, а не ошибка unresolved symbol. – Pavel Mayorov Oct 14 '16 at 13:38
  • @PavelMayorov ну а теперь на вопрос посмотрите и логи, которые привел автор. – αλεχολυτ Oct 14 '16 at 13:40
  • Это вы посмотрите. Там 1 предупреждение LNK4098 - и 13 ошибок, связанных с WinAPI (не с рантаймом!). – Pavel Mayorov Oct 14 '16 at 13:43
  • В решении для шаблонного друга, вероятно, имелось в виду friend void f(C<V>);. Пример. – tocic Jun 04 '23 at 08:19
  • Собирал либу я сам через nmake. На том же компе и пытался импортировать с тем же компилятором. Указал дополнительные каталоги включаемые (для .h файлов), он видит их. Указал каталог для библиотек в компоновщике и название lib файла в доп зависимостях. Перебирал разные флаги /MDd /MTd /MD /MT. Перебирал release, debug, x86, x64. При x86 варнинг что либа 64 битная, но символы как не видел так и не видит. Если заглянуть в dumpbin /symbols то там нужные символы есть и экспортированы. Так же пробовал дописать __declspec(dllexport) в код либы, не помогло. Может кто-нибудь помочь? Либа PQClean. – Mr Lucky Tomas Jul 19 '23 at 00:58
  • @MrLuckyTomas предлагаю опубликовать это отдельным вопросом. – αλεχολυτ Jul 19 '23 at 05:47
  • @αλεχολυτ видимо дело не во мне, а в либе. Они мне предложили попробовать другую, в которой лучше реализована поддержка винды и я сделал все ровно так же как и с предыдущими либами, но теперь все компилируется и работает как надо. – Mr Lucky Tomas Jul 20 '23 at 02:25
18

(В дополнение к уже существующему ответу)

  • При использовании Visual Studio или других компиляторов для платформы Windows выполнение проекта типа Windows Desktop Application начинается с функции WinMain, а выполнение проекта типа Windows Console Application начинается с функции main.

    Если ваша программа написана на обычном С или С++, т.е содержит стандартную классическую функцию main, а вы по ошибке создали свой проект с типом Windows Desktop Application, то при линковке вы получите сообщение о том, что функция WinMain не была найдена линкером.

    И наоборот, если вы создали проект типа Windows Console Application, но вместо функции main определили в нем функцию WinMain, то при линковке вы получите сообщение о том, что функция main не была найдена линкером.

4

Дополнение к ответу αλεχολυτ - как оказалось, некоторые ухитряются объявить чисто виртуальным деструктор:

class Base {
    ...
    virtual ~Base() = 0;

а такие фокусы недопустимы, так как деструктор базового класса вызывается всегда (кроме случая создания объекта через new без соответствующего delete). Результат - та же ошибка undefined reference.

Еще одно дополнение — в Visual C++ получение сообщения

Ошибка LNK2019 ссылка на неразрешенный внешний символ WinMain в функции "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ).

Связана с тем, что приложение, написанное, как консольное (с int main()) собирается как оконное. Исправьте, войдя в Проект→Компоновщик→Система→Подсистема и выбрав в списке Консоль (/SUBSYSTEM:CONSOLE).

Добавил исключительно для тех, кто будет искать на странице int __cdecl invoke_main :)

Harry
  • 221,325
  • 1
    если что, чисто виртуальный деструктор — обычный и совершенно нормальный способ сделать класс чисто виртуальным (и запретить его создание)... просто его надо определить... – Fat-Zer Aug 13 '21 at 12:48
  • 1
    @Fat-Zer, и куда его потом девать? Ведь унаследованные от него классы тоже не будут работать? – Qwertiy Oct 23 '21 at 18:13
  • @Qwertiy, конечно, будут... почему нет? – Fat-Zer Oct 23 '21 at 22:14
  • @Fat-Zer, по тому, что в этом ответе написано. – Qwertiy Oct 23 '21 at 22:15
  • @Qwertiy, УМВР: https://godbolt.org/z/ed7EbW969 – Fat-Zer Oct 23 '21 at 22:23
  • @Harry, как так? – Qwertiy Oct 23 '21 at 22:46
  • @Qwertiy Что здесь не так, как написано у меня? https://ideone.com/qPl7Ud А по ссылке — ну, пусть он его слинкует и покажет... Вопрос ведь у нас — напомню — "ссылка на неразрешенный внешний символ"... – Harry Oct 24 '21 at 05:58
  • 1
    @Fat-Zer, а почему у тебя одновременно и =0, и Base::~Base() {} в коде? Я только сейчас этот финт заметил. – Qwertiy Oct 24 '21 at 11:01
  • @Qwertiy Мда, и я тоже :) Кстати, а что говорит об этом стандарт? О наличии тела у чисто виртуальной функции? Компиляторы-то обычно это позволяют, а стандарт? – Harry Oct 24 '21 at 11:13
  • @Qwertiy, я же сразу написал: просто его надо определить – Fat-Zer Oct 24 '21 at 12:18
  • 1
    @Harry, [class.virtual]/11: A virtual function declared in a class shall be defined, or declared pure in that class, or both; no diagnostic is required... по поводу ссылки: там и линковка и запуск включены в настройках... – Fat-Zer Oct 24 '21 at 12:19
  • я, если честно, считал, что это всё «общеизвестная тонкость», поэтому как-то и не заострял на этом внимания... – Fat-Zer Oct 24 '21 at 12:40
  • @Fat-Zer Меня как-то раз здесь били по рукам, когда я снабдил такую чисто виртуальную функцию телом, так что я решил, что это стандартом не разрешено, но работает :) – Harry Oct 24 '21 at 13:12
2

Отдельным случаем я бы выделил полное отсутствие точки входа при компоновке запускаемого модуля. Другими словами: программист хочет собрать запускаемую программу, а в исходном коде отсутствует функция main():

Программа должна иметь глобальную функцию main, которая является точкой старта программы в гостевой среде

чаще всего имеющую сигнатуру:

int main() { ... }

или

int main(int argc, char *argv[]) { ... }

Ситуация с отсутствием функции main() может возникнуть просто по невнимательности, но чаще всего случается при копировании примеров из интернета, книг и других источников, когда приведенный фрагмент кода является отдельной функцией.

Типичный симптом: сообщения об ошибках, упоминающих неразрешенный внешний символ main().

Пример вопросов:

1

Возможно, это и так все знают, но на всякий случай. Использование inline конструкторов и методов, прописанных в отдельном файле, также может вызывать подобные ошибки. То есть недопустимо писать в .cpp файле такое (для MSVS2022):

inline records::records(void) {
    records_amount_ = 0;
    first_record_p_ = nullptr;
}

а нужно писать:

records::records(void) {
    records_amount_ = 0;
    first_record_p_ = nullptr;
}
dzian_cpp
  • 11
  • 2