Что такое неопределенные ссылки / неразрешенные ошибки внешних символов? Каковы общие причины и как их исправить / предотвратить?
Не стесняйтесь редактировать / добавлять свои собственные.
Компиляция программы на C ++ происходит в несколько этапов, как указано в 2,2 (кредиты Кита Томпсон для справки):
Приоритет среди синтаксических правил перевода определяется следующими этапами [см. сноску].
- Физические символы исходного файла отображаются, в зависимости от реализации, в основной исходный набор символов
(введение символов новой строки для индикаторов конца строки), если
необходимо. [СНиП]- Каждый экземпляр символа обратной косой черты (\), за которым сразу следует символ новой строки, удаляется, объединяя строки физического источника в
сформировать логические строки источника. [СНиП]- Исходный файл разлагается на токены предварительной обработки (2.5) и последовательности символов пробела (включая комментарии). [СНиП]
- Выполняются директивы предварительной обработки, расширяются вызовы макросов и выполняются выражения унарного оператора _Pragma. [СНиП]
- Каждый элемент исходного набора символов в символьном литерале или строковом литерале, а также каждая escape-последовательность и универсальное имя-символа
в символьном литерале или не необработанном строковом литерале, преобразуется в
соответствующий член набора символов выполнения; [СНиП]- Литеральные токены смежных строк объединяются.
- Пробелы, разделяющие токены, больше не имеют значения. Каждый токен предварительной обработки преобразуется в токен. (2.7).
полученные токены синтаксически и семантически анализируются и
переводится как единица перевода. [СНиП]- Переведенные единицы перевода и единицы реализации объединяются следующим образом: [СНиП]
- Все ссылки на внешние объекты разрешены. Компоненты библиотеки связаны для удовлетворения внешних ссылок на объекты, не определенные в
текущий перевод. Все такие выходные данные переводчика собираются в
образ программы, который содержит информацию, необходимую для выполнения в своем
среда исполнения. (акцент мой)[Примечание] Реализации должны вести себя так, как если бы эти отдельные фазы происходили, хотя на практике разные фазы могут складываться вместе.
Указанные ошибки возникают на последнем этапе компиляции, который чаще всего называют связыванием. По сути, это означает, что вы скомпилировали кучу файлов реализации в объектные файлы или библиотеки и теперь хотите, чтобы они работали вместе.
Скажем, вы определили символ a
в a.cpp
, Сейчас, b.cpp
объявленный этот символ и использовал его. Перед установкой ссылки просто предполагается, что этот символ был определен где-то, но это все равно где. Фаза связывания отвечает за поиск символа и правильное связывание его с b.cpp
(ну, собственно, к объекту или библиотеке, которые его используют).
Если вы используете Microsoft Visual Studio, вы увидите, что проекты генерируют .lib
файлы. Они содержат таблицу экспортируемых символов и таблицу импортированных символов. Импортированные символы сопоставляются с библиотеками, с которыми вы ссылаетесь, а экспортируемые символы предоставляются для библиотек, которые используют .lib
(если есть).
Подобные механизмы существуют для других компиляторов / платформ.
Распространенные сообщения об ошибках error LNK2001
, error LNK1120
, error LNK2019
за Microsoft Visual Studio а также undefined reference to
SymbolName за НКУ.
Код:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
struct A
{
virtual ~A() = 0;
};
struct B: A
{
virtual ~B(){}
};
extern int x;
void foo();
int main()
{
x = 0;
foo();
Y y;
B b;
}
будет генерировать следующие ошибки с НКУ:
/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x10): undefined reference to `x'
prog.cpp:(.text+0x19): undefined reference to `foo()'
prog.cpp:(.text+0x2d): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD1Ev[B::~B()]+0xb): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o: In function `B::~B()':
prog.cpp:(.text._ZN1BD0Ev[B::~B()]+0x12): undefined reference to `A::~A()'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1Y[typeinfo for Y]+0x8): undefined reference to `typeinfo for X'
/home/AbiSfw/ccvvuHoX.o:(.rodata._ZTI1B[typeinfo for B]+0x8): undefined reference to `typeinfo for A'
collect2: ld returned 1 exit status
и подобные ошибки с Microsoft Visual Studio:
1>test2.obj : error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
1>test2.obj : error LNK2001: unresolved external symbol "int x" (?x@@3HA)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
1>test2.obj : error LNK2001: unresolved external symbol "public: virtual void __thiscall X::foo(void)" (?foo@X@@UAEXXZ)
1>...\test2.exe : fatal error LNK1120: 4 unresolved externals
Общие причины включают в себя:
#pragma
(Microsoft Visual Studio)UNICODE
определенияvirtual
деструктор нуждается в реализации.Чтобы объявить деструктор чистым, вы все равно должны его определить (в отличие от обычной функции):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
Это происходит потому, что деструкторы базового класса вызываются, когда объект уничтожается неявно, поэтому требуется определение.
virtual
методы должны быть либо реализованы, либо определены как чистые.Это похоже наvirtual
методы без определения, с дополнительным обоснованием того, что
Чистое объявление генерирует фиктивную таблицу, и вы можете получить ошибку компоновщика, не используя функцию:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Чтобы это работало, объявляем X::foo()
как чистый:
struct X
{
virtual void foo() = 0;
};
virtual
ученикиНекоторые члены должны быть определены, даже если они не используются явно:
struct A
{
~A();
};
Следующее приведет к ошибке:
A a; //destructor undefined
Реализация может быть встроенной в самом определении класса:
struct A
{
~A() {}
};
или снаружи:
A::~A() {}
Если реализация находится за пределами определения класса, но в заголовке, методы должны быть помечены как inline
предотвратить множественное определение.
Все используемые методы-члены должны быть определены, если они используются.
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
Определение должно быть
void A::foo() {}
static
члены данных должны быть определены вне класса в единая единица перевода:struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
Инициализатор может быть предоставлен для static
const
член данных целочисленного или перечислимого типа в определении класса; однако для использования odr этого члена все равно потребуется определение области имен, как описано выше. C ++ 11 позволяет инициализацию внутри класса для всех static const
члены данных.
Обычно каждая единица перевода генерирует объектный файл, который содержит определения символов, определенных в этой единице перевода.
Чтобы использовать эти символы, вы должны ссылаться на эти объектные файлы.
Под НКУ Вы должны указать все объектные файлы, которые должны быть связаны вместе в командной строке, или скомпилировать файлы реализации вместе.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
libraryName
Вот только голое название библиотеки, без специфических для платформы дополнений. Так, например в Linux файлы библиотеки обычно называются libfoo.so
но ты бы только написал -lfoo
, В Windows этот же файл может называться foo.lib
, но вы бы использовали тот же аргумент. Возможно, вам придется добавить каталог, где эти файлы могут быть найдены с помощью -L‹directory›
, Убедитесь, что не написали пробел после -l
или же -L
,
За XCode: Добавить пути поиска в заголовке пользователя -> добавить путь поиска в библиотеке -> перетащить фактическую ссылку на библиотеку в папку проекта.
Под МСВС, файлы, добавленные в проект, автоматически связывают свои объектные файлы и lib
файл будет создан (в общем использовании). Чтобы использовать символы в отдельном проекте, вы бы
необходимо включить lib
файлы в настройках проекта. Это делается в разделе Linker свойств проекта, в Input -> Additional Dependencies
, (путь к lib
файл должен быть
добавлено в Linker -> General -> Additional Library Directories
) При использовании сторонней библиотеки, которая поставляется с lib
файл, неспособность сделать это обычно приводит к ошибке.
Также может случиться, что вы забудете добавить файл в компиляцию, и в этом случае объектный файл не будет сгенерирован. В НКУ вы бы добавили файлы в командную строку. В МСВС добавление файла в проект автоматически скомпилирует его (хотя файлы можно вручную исключить из сборки по отдельности).
В программировании Windows контрольным признаком того, что вы не связали необходимую библиотеку, является то, что имя неразрешенного символа начинается с __imp_
, Посмотрите название функции в документации, и там должно быть указано, какую библиотеку вам нужно использовать. Например, MSDN помещает информацию в поле внизу каждой функции в разделе «Библиотека».
Типичное объявление переменной
extern int x;
Поскольку это только декларация, единое определение нужно. Соответствующее определение будет:
int x;
Например, следующее может привести к ошибке:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Аналогичные замечания относятся к функциям. Объявление функции без ее определения приводит к ошибке:
void foo(); // declaration only
int main()
{
foo();
}
//void foo() {} //uncomment this line for successful definition
Будьте осторожны, чтобы реализуемая вами функция точно соответствовала той, которую вы объявили. Например, вы могли не соответствовать cv-квалификаторам:
void foo(int& x);
int main()
{
int x;
foo(x);
}
void foo(const int& x) {} //different function, doesn't provide a definition
//for void foo(int& x)
Другие примеры несоответствий включают
Сообщение об ошибке от компилятора часто дает вам полное объявление переменной или функции, которая была объявлена, но никогда не определялась. Сравните это с приведенным вами определением. Убедитесь, что каждая деталь соответствует.
Порядок, в котором связаны библиотеки, имеет значение, если библиотеки зависят друг от друга. В общем, если библиотека A
зависит от библиотеки B
, затем libA
ДОЛЖЕН появиться раньше libB
в компоновщике флагов.
Например:
// B.h
#ifndef B_H
#define B_H
struct B {
B(int);
int x;
};
#endif
// B.cpp
#include "B.h"B::B(int xx) : x(xx) {}
// A.h
#include "B.h"
struct A {
A(int x);
B b;
};
// A.cpp
#include "A.h"
A::A(int x) : b(x) {}
// main.cpp
#include "A.h"
int main() {
A a(5);
return 0;
};
Создайте библиотеки:
$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o
ar: creating libB.a
a - B.o
Обобщение:
$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out
Так что повторить еще раз, порядок ДЕЛАЕТ иметь значение!
что такое «неопределенная ссылка / неразрешенный внешний символ»
Я попытаюсь объяснить, что такое «неопределенная ссылка / неразрешенный внешний символ».
примечание: я использую g ++ и Linux, и все примеры для него
Например, у нас есть код
// src1.cpp
void print();
static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;
int main()
{
print();
return 0;
}
а также
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
//extern int local_var_name;
void print ()
{
// printf("%d%d\n", global_var_name, local_var_name);
printf("%d\n", global_var_name);
}
Сделать объектные файлы
$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
После фазы ассемблера у нас есть объектный файл, который содержит любые символы для экспорта.
Посмотрите на символы
$ readelf --symbols src1.o
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1]
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]
Я отклонил некоторые строки из вывода, потому что они не имеют значения
Итак, мы видим следующие символы для экспорта.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp ничего не экспортирует, и мы не видели его символов
Связать наши объектные файлы
$ g++ src1.o src2.o -o prog
и запустить его
$ ./prog
123
Линкер видит экспортированные символы и связывает их. Теперь мы пытаемся раскомментировать строки в src2.cpp, как здесь
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
extern int local_var_name;
void print ()
{
printf("%d%d\n", global_var_name, local_var_name);
}
и восстановить объектный файл
$ g++ -c src2.cpp -o src2.o
ОК (без ошибок), потому что мы только строим объектный файл, связывание еще не завершено.
Попробуй ссылку
$ g++ src1.o src2.o -o prog
src2.o: In function `print()':
src2.cpp:(.text+0x6): undefined reference to `local_var_name'
collect2: error: ld returned 1 exit status
Это произошло потому, что наше local_var_name является статическим, то есть оно невидимо для других модулей.
Теперь глубже. Получите вывод фазы перевода
$ g++ -S src1.cpp -o src1.s
// src1.s
look src1.s
.file "src1.cpp".local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
.globl global_var_name
.data
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; assembler code, not interesting for us
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2".section .note.GNU-stack,"",@progbits
Итак, мы видели, что нет метки для local_var_name, поэтому компоновщик не нашел ее. Но мы хакеры 🙂 и мы можем это исправить. Откройте src1.s в вашем текстовом редакторе и измените
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
в
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
то есть вы должны иметь как ниже
.file "src1.cpp".globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
.globl global_var_name
.align 4
.type global_var_name, @object
.size global_var_name, 4
global_var_name:
.long 123
.text
.globl main
.type main, @function
main:
; ...
мы изменили видимость local_var_name и установили его значение на 456789.
Попробуйте построить из него объектный файл
$ g++ -c src1.s -o src2.o
хорошо, смотрите вывод readelf (символы)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
теперь у local_var_name есть Bind GLOBAL (был LOCAL)
ссылка на сайт
$ g++ src1.o src2.o -o prog
и запустить его
$ ./prog
123456789
хорошо, мы взломали это 🙂
Таким образом, в результате — «неопределенная ссылка / неразрешенная ошибка внешнего символа» происходит, когда компоновщик не может найти глобальные символы в объектных файлах.
Функция (или переменная) void foo()
был определен в программе на C, и вы пытаетесь использовать его в программе на C ++:
void foo();
int main()
{
foo();
}
Компоновщик C ++ ожидает искажения имен, поэтому вы должны объявить функцию следующим образом:
extern "C" void foo();
int main()
{
foo();
}
Эквивалентно, вместо того, чтобы быть определенным в программе на C, функция (или переменная) void foo()
был определен в C ++, но с привязкой C:
extern "C" void foo();
и вы пытаетесь использовать его в программе C ++ со связью C ++.
Если вся библиотека включена в заголовочный файл (и была скомпилирована как код C); включение должно быть следующим;
extern "C" {
#include "cheader.h"}
Если ничего не помогает, перекомпилируйте.
Недавно я смог избавиться от нерешенной внешней ошибки в Visual Studio 2012, просто перекомпилировав файл-нарушитель. Когда я перестроил, ошибка ушла.
Это обычно происходит, когда две (или более) библиотеки имеют циклическую зависимость. Библиотека A пытается использовать символы из B.lib, а библиотека B пытается использовать символы из A.lib. Ни один не существует, чтобы начать с. Когда вы попытаетесь скомпилировать A, шаг ссылки потерпит неудачу, потому что он не может найти B.lib. A.lib будет сгенерирован, но не dll. Затем вы компилируете B, который преуспеет и сгенерирует B.lib. Перекомпиляция A теперь будет работать, потому что B.lib теперь найден.