Что такое неопределенная ссылка / неразрешенная внешняя ошибка символа и как ее исправить?

1493

Что такое неопределенные ссылки / неразрешенные внешние ошибки символов? Каковы общие причины и как их исправить / предотвратить?

Не стесняйтесь редактировать / добавлять свои собственные.

Лучиан Григоре
источник
@LuchianGrigore «не стесняйтесь добавлять ответ» Я предпочел добавить соответствующую ссылку (ИМХО) вашего основного ответа, если вы хотите, чтобы разрешить.
πάντα ῥεῖ
19
Этот вопрос слишком общий, чтобы допустить полезный ответ. Я не могу поверить количеству людей с репутацией более 1000, которые комментируют это, не отбрасывая вопрос. В то же время я вижу много вопросов, которые являются разумными и очень полезными для меня, которые закрыты.
Альберт ван дер Хорст
10
@AlbertvanderHorst «Этот вопрос слишком общий, чтобы допустить полезный ответ». Разве ответы ниже бесполезны?
LF
4
Они могут быть полезны, как и многие ответы на вопросы, которые помечены как слишком общие.
Альберт ван дер Хорст
1
Честно говоря, я хотел бы видеть минимальный воспроизводимый пример как нечто, о чем мы просим большинство новых пользователей. Я ничего не имею в виду, это просто - мы не можем ожидать, что люди будут следовать правилам, которые мы сами не информируем.
Данило

Ответы:

850

Компиляция программы на C ++ происходит в несколько этапов, как указано в 2.2 (благодарность Кита Томпсону за справку) :

Приоритет среди синтаксических правил перевода определяется следующими этапами [см. Сноску] .

  1. Физические символы исходного файла отображаются, в соответствии с реализацией, в базовый исходный набор символов (ввод символов новой строки для индикаторов конца строки), если это необходимо. [СНиП]
  2. Каждый экземпляр символа обратной косой черты (\), за которым сразу следует символ новой строки, удаляется, объединяя физические исходные строки для формирования логических исходных строк. [СНиП]
  3. Исходный файл разбит на токены предварительной обработки (2.5) и последовательности символов пробела (включая комментарии). [СНиП]
  4. Выполняются директивы предварительной обработки, расширяются вызовы макросов и выполняются выражения унарного оператора _Pragma. [СНиП]
  5. Каждый элемент исходного набора символов в символьном литерале или строковом литерале, а также каждая escape-последовательность и универсальное имя-символа в символьном литерале или строковом литерале, не являющемся необработанным, преобразуются в соответствующий элемент набора исполняемых символов; [СНиП]
  6. Литеральные токены смежных строк объединяются.
  7. Пробельные символы, разделяющие токены, больше не имеют значения. Каждый токен предварительной обработки преобразуется в токен. (2.7). Полученные токены синтаксически и семантически анализируются и переводятся как единица перевода. [СНиП]
  8. Переведенные единицы перевода и единицы реализации объединяются следующим образом: [SNIP]
  9. Все ссылки на внешние объекты разрешены. Компоненты библиотеки связаны для удовлетворения внешних ссылок на объекты, не определенные в текущем переводе. Весь такой вывод транслятора собирается в образ программы, который содержит информацию, необходимую для выполнения в среде выполнения. (акцент мой)

[Примечание] Реализации должны вести себя так, как если бы эти отдельные фазы происходили, хотя на практике разные фазы могут складываться вместе.

Указанные ошибки возникают на этом последнем этапе компиляции, который чаще всего называют связыванием. По сути, это означает, что вы скомпилировали кучу файлов реализации в объектные файлы или библиотеки, и теперь вы хотите, чтобы они работали вместе.

Скажем, вы определили символ 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 для GCC .

Код:

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;
}

сгенерирует следующие ошибки с GCC :

/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

Общие причины включают в себя:

Лучиан Григоре
источник
16
Лично я думаю, что сообщения об ошибках компоновщика MS так же читаемы, как и ошибки GCC. У них также есть преимущество включения как искаженных, так и не искаженных имен для неразрешенного внешнего. Наличие искаженного имени может быть полезно, когда вам нужно посмотреть на библиотеки или объектные файлы напрямую, чтобы увидеть, в чем может быть проблема (например, несоответствие соглашения о вызовах). Кроме того, я не уверен, какая версия MSVC породила ошибки здесь, но более новые версии включают имя (как искаженное, так и не исправленное) функции, относящейся к неразрешенному внешнему символу.
Майкл Берр
5
Дэвид Драйсдейл написал отличную статью о том, как работают линкеры: Руководство для начинающих по линкерам . Учитывая тему этого вопроса, я подумал, что это может оказаться полезным.
Прессакко
@TankorSmash Использовать gcc? MinGW, чтобы быть более точным.
doug65536
1
@luchian было бы неплохо, если бы вы добавили правильное, исправив вышеперечисленные ошибки
Phalgun
178

Члены класса:

Чистому virtualдеструктору нужна реализация.

Чтобы объявить деструктор чистым, вы все равно должны его определить (в отличие от обычной функции):

struct X
{
    virtual ~X() = 0;
};
struct Y : X
{
    ~Y() {}
};
int main()
{
    Y y;
}
//X::~X(){} //uncomment this line for successful definition

Это происходит потому, что деструкторы базового класса вызываются, когда объект уничтожается неявно, поэтому требуется определение.

virtual методы должны быть либо реализованы, либо определены как чистые.

Это похоже наvirtual методы без определения, с дополнительным обоснованием того, что чистое объявление генерирует фиктивную vtable, и вы можете получить ошибку компоновщика без использования функции:

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членов данных.

Лучиан Григоре
источник
Просто подумал, что вы, возможно, захотите подчеркнуть, что выполнение того и другого возможно, и dtor на самом деле не исключение. (это не очевидно из вашей формулировки на первый взгляд.)
Deduplicator
112

Ошибка связывания с соответствующими библиотеками / объектными файлами или компиляцией файлов реализации.

Обычно каждая единица перевода генерирует объектный файл, который содержит определения символов, определенных в этой единице перевода. Чтобы использовать эти символы, вы должны ссылаться на эти объектные файлы.

В gcc вы должны указать все объектные файлы, которые должны быть связаны вместе в командной строке, или скомпилировать файлы реализации вместе.

g++ -o test objectFile1.o objectFile2.o -lLibraryName

libraryNameЗдесь просто голое имя библиотеки, без платформы конкретных дополнений. Так, например, в Linux файлы библиотеки обычно называются, libfoo.soно вы только пишете -lfoo. В Windows этот же файл может быть вызван foo.lib, но вы будете использовать тот же аргумент. Возможно, вам придется добавить каталог, где эти файлы могут быть найдены с помощью -L‹directory›. Убедитесь, что не пишете пробел после -lили-L .

Для XCode : добавьте пути поиска по заголовку пользователя -> добавьте путь поиска по библиотеке -> перетащите фактическую ссылку на библиотеку в папку проекта.

В MSVS файлы, добавленные в проект, автоматически связывают свои объектные файлы, и libфайл будет создан (в обычном использовании). Чтобы использовать символы в отдельном проекте, вам необходимо включить libфайлы в настройки проекта. Это делается в разделе Linker свойств проекта, в Input -> Additional Dependencies. (путь к libфайлу должен быть добавлен Linker -> General -> Additional Library Directories) При использовании сторонней библиотеки, которая поставляется сlib файлом, невозможность сделать это обычно приводит к ошибке.

Также может случиться, что вы забудете добавить файл в компиляцию, и в этом случае объектный файл не будет сгенерирован. В gcc вы добавляете файлы в командную строку. В MSVS добавлении файла в проект сделает его скомпилировать его автоматически (хотя файлы можно вручную, индивидуально исключены из сборки).

В программировании Windows контрольным признаком того, что вы не связали необходимую библиотеку, является то, что имя неразрешенного символа начинается с __imp_. Посмотрите название функции в документации, и там должно быть указано, какую библиотеку вам нужно использовать. Например, MSDN помещает информацию в поле внизу каждой функции в разделе «Библиотека».

Лучиан Григоре
источник
1
Было бы хорошо, если бы вы могли явно скрыть типичную ошибку gcc main.cвместо gcc main.c other.c (что часто делают новички до того, как их проекты становятся такими большими, чтобы создавать файлы .o).
ММ
101

Объявлен, но не определил переменную или функцию.

Типичное объявление переменной

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)

Другие примеры несоответствий включают

  • Функция / переменная объявлена ​​в одном пространстве имен, определена в другом.
  • Функция / переменная объявлена ​​как член класса, определена как глобальная (или наоборот).
  • Тип возвращаемого значения функции, номер и типы параметров, а также соглашение о вызовах не полностью совпадают.

Сообщение об ошибке от компилятора часто дает вам полное объявление переменной или функции, которая была объявлена, но никогда не определялась. Сравните это с приведенным вами определением. Убедитесь, что каждая деталь соответствует.

Лучиан Григоре
источник
В VS файлы cpp, совпадающие с заголовками, #includesне добавленными в исходный каталог, также попадают в категорию отсутствующих определений.
Лори Стерн
86

Порядок, в котором указываются взаимозависимые связанные библиотеки, неверен.

Порядок, в котором связаны библиотеки, имеет значение, если библиотеки зависят друг от друга. Как правило, если библиотека 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

Так что еще раз повторить, порядок ДЕЛАЕТ дело!

Svalorzen
источник
Мне любопытно, что в моем случае у меня был один объектный файл, который зависит от общей библиотеки. Мне пришлось изменить Makefile и поместить библиотеку ПОСЛЕ объекта с gcc 4.8.4 в Debian. На Centos 6.5 с gcc 4.4 Makefile работал без проблем.
Марко Сулла
74

что такое «неопределенная ссылка / неразрешенный внешний символ»

Я попытаюсь объяснить, что такое «неопределенная ссылка / неразрешенный внешний символ».

примечание: я использую 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

хорошо, мы взломали это :)

Таким образом, в результате - «неопределенная ссылка / неразрешенная ошибка внешнего символа» происходит, когда компоновщик не может найти глобальные символы в объектных файлах.

Кастанеда
источник
71

Символы были определены в программе на C и использованы в коде C ++.

Функция (или переменная) 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"
}
Лучиан Григоре
источник
5
Или, наоборот, если вы разрабатываете библиотеку C, хорошим правилом является защита заголовочных файлов, окружая все экспортированные объявления символами #ifdef __cplusplus [\n] extern"C" { [\n] #endifи #ifdef __cplusplus [\n] } [\n] #endif([\n] будучи реальным возвратом каретки, но я не могу написать это правильно в комментарии).
Bentoy13
Как и в предыдущем комментарии, здесь помог раздел «Создание заголовков на разных языках»:
разных языках»
Действительно помог мне! Это был мой случай, когда я искал этот вопрос
knightofhorizon
Это также может произойти, если вы случайно включили ваш обычный заголовочный файл C ++ в окружении extern C : extern "C" { #include <myCppHeader.h> }.
ComFreek
68

Если ничего не помогает, перекомпилируйте.

Недавно я смог избавиться от нерешенной внешней ошибки в Visual Studio 2012, просто перекомпилировав файл, который нарушил работу. Когда я перестроил, ошибка ушла.

Это обычно происходит, когда две (или более) библиотеки имеют циклическую зависимость. Библиотека A пытается использовать символы из B.lib, а библиотека B пытается использовать символы из A.lib. Ни один не существует, чтобы начать с. Когда вы попытаетесь скомпилировать A, шаг ссылки потерпит неудачу, потому что он не может найти B.lib. A.lib будет сгенерирован, но не dll. Затем вы компилируете B, который преуспеет и сгенерирует B.lib. Перекомпиляция A теперь будет работать, потому что B.lib теперь найден.

sgryzko
источник
1
Правильно - это происходит, когда библиотеки имеют циклическую зависимость.
Лучиан Григоре
1
Я расширил ваш ответ и включил основной. Спасибо.
Лучиан Григоре
57

Неправильный импорт / экспорт методов / классов через модули / dll (зависит от компилятора).

MSVS требует от вас указать, какие символы экспортировать и импортировать, используя __declspec(dllexport)и__declspec(dllimport) .

Эта двойная функциональность обычно получается с помощью макроса:

#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif

Макрос THIS_MODULEбудет определен только в модуле, который экспортирует функцию. Таким образом, декларация:

DLLIMPEXP void foo();

расширяется до

__declspec(dllexport) void foo();

и указывает компилятору экспортировать функцию, так как текущий модуль содержит ее определение. Если включить объявление в другой модуль, оно будет расширено до

__declspec(dllimport) void foo();

и сообщает компилятору, что определение находится в одной из библиотек, с которыми вы связаны (см. также 1) ).

Вы можете аналогичные классы импорта / экспорта:

class DLLIMPEXP X
{
};
Лучиан Григоре
источник
2
Чтобы быть полным, в этом ответе должны быть указаны файлы GCC visibilityи Windows .def, так как они также влияют на имя и присутствие символа.
rubenvb
@rubenvb Я давно не использовал .defфайлы. Не стесняйтесь добавлять ответ или редактировать этот.
Лучиан Григоре
57

Это одно из самых запутанных сообщений об ошибках, которые каждый VC ++ программисты видели снова и снова. Давайте сначала проясним ситуацию.

А. Что такое символ? Короче говоря, символ - это имя. Это может быть имя переменной, имя функции, имя класса, имя typedef или что угодно, кроме тех имен и знаков, которые принадлежат языку C ++. Он определяется пользователем или вводится библиотекой зависимостей (другой определяется пользователем).

B. Что является внешним? В VC ++ каждый исходный файл (.cpp, .c и т. (Обратите внимание, что каждый заголовочный файл, включенный в этот исходный файл, будет предварительно обработан и будет считаться частью этого модуля перевода) Все в модуле перевода рассматривается как внутреннее, все остальное рассматривается как внешнее. В C ++ вы можете ссылаться на внешний символ, используя такие ключевые слова, как extern, __declspec (dllimport)и так далее.

C. Что такое «решимость»? Resolve - это термин времени связывания. Во время компоновки компоновщик пытается найти внешнее определение для каждого символа в объектных файлах, которые не могут найти его определение внутри. Объем этого процесса поиска, включая:

  • Все объектные файлы, созданные во время компиляции
  • Все библиотеки (.lib), которые явно или неявно указаны как дополнительные зависимости этого строительного приложения.

Этот процесс поиска называется решимостью.

D. Наконец, почему неразрешенный внешний символ? Если компоновщик не может найти внешнее определение для символа, который не имеет внутреннего определения, он сообщает об ошибке «Неразрешенный внешний символ».

E. Возможные причины LNK2019 : Неразрешенная ошибка внешнего символа. Мы уже знаем, что эта ошибка связана с тем, что компоновщику не удалось найти определение внешних символов, возможные причины можно отсортировать по следующим параметрам:

  1. Определение существует

Например, если у нас есть функция с именем foo, определенная в a.cpp:

int foo()
{
    return 0;
}

В b.cpp мы хотим вызвать функцию foo, поэтому добавим

void foo();

чтобы объявить функцию foo () и вызвать ее в другом теле функции, скажем bar():

void bar()
{
    foo();
}

Теперь, когда вы создаете этот код, вы получите ошибку LNK2019 с жалобой на то, что foo является неразрешенным символом. В этом случае мы знаем, что foo () имеет свое определение в a.cpp, но отличается от того, который мы вызываем (другое возвращаемое значение). Это тот случай, когда определение существует.

  1. Определение не существует

Если мы хотим вызвать некоторые функции в библиотеке, но библиотека импорта не добавляется в дополнительный список зависимостей (устанавливается из:) Project | Properties | Configuration Properties | Linker | Input | Additional Dependencyвашего проекта. Теперь компоновщик сообщит LNK2019, так как определение не существует в текущей области поиска.

Нима Соруш
источник
1
Спасибо за систематическое объяснение. Я мог понять другие ответы здесь после прочтения этого.
displayName
56

Реализации шаблона не видны.

Неспециализированные шаблоны должны иметь свои определения, видимые для всех единиц перевода, которые их используют. Это означает, что вы не можете отделить определение шаблона от файла реализации. Если вы должны отделить реализацию, обычный обходной путь должен иметь implфайл, который вы включаете в конец заголовка, который объявляет шаблон. Распространенная ситуация:

template<class T>
struct X
{
    void foo();
};

int main()
{
    X<int> x;
    x.foo();
}

//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}

Чтобы это исправить, вы должны переместить определение X::foo в файл заголовка или в другое место, видимое для модуля перевода, который его использует.

Специализированные шаблоны могут быть реализованы в файле реализации, и реализация не должна быть видимой, но специализация должна быть предварительно объявлена.

Для дальнейшего объяснения и другого возможного решения (явной реализации) см. Этот вопрос и ответ .

Лучиан Григоре
источник
1
Дополнительную информацию для «шаблонов должны быть определены в заголовке» можно найти в stackoverflow.com/questions/495021
PlasmaHH
41

неопределенная ссылка WinMain@16или похожая «необычная» main() ссылка на точку входа (особенно для).

Возможно, вы пропустили выбор правильного типа проекта с вашей фактической IDE. В среде IDE может потребоваться привязать, например, проекты приложений Windows, к такой функции точки входа (как указано в отсутствующей ссылке выше) вместо обычно используемой int main(int argc, char** argv);подписи.

Если ваша IDE поддерживает простые консольные проекты, вы можете выбрать этот тип проекта вместо проекта Windows.


Здесь case1 и case2 рассматриваются более подробно из реальной проблемы.

πάντα ῥεῖ
источник
2
Не могу не отметить этот вопрос и тот факт, что это чаще всего вызвано отсутствием основной функции, чем отсутствием WinMain. Действительные программы на C ++ нуждаются в main.
Крис
36

Также, если вы используете сторонние библиотеки, убедитесь, что у вас есть правильные 32/64-битные двоичные файлы

Дула
источник
34

Microsoft предлагает #pragmaссылаться на правильную библиотеку во время ссылки;

#pragma comment(lib, "libname.lib")

В дополнение к пути к библиотеке, включая каталог библиотеки, это должно быть полное имя библиотеки.

Найл
источник
34

Пакет Visual Studio NuGet необходимо обновить для новой версии набора инструментов

Я только что столкнулся с этой проблемой при попытке связать libpng с Visual Studio 2013. Проблема в том, что файл пакета содержал библиотеки только для Visual Studio 2010 и 2012.

Правильное решение - надеяться, что разработчик выпустит обновленный пакет, а затем обновит его, но он сработал для меня, взломав дополнительный параметр для VS2013, указывая на файлы библиотеки VS2012.

Я отредактировал пакет (в packagesпапке внутри каталога решения), найдя packagename\build\native\packagename.targetsи внутри этого файла, скопировав все v110разделы. Я изменил v110к v120в полях состояния только очень осторожно , чтобы оставить пути имени файла все как v110. Это просто позволило Visual Studio 2013 ссылаться на библиотеки на 2012 год, и в этом случае это работало.

Malvineous
источник
1
Это кажется слишком конкретным - возможно, новый ответ будет лучшим местом для этого ответа.
Лучиан Григоре
3
@LuchianGrigore: Я действительно хотел опубликовать здесь, так как этот вопрос был именно этой проблемой, но он был помечен как дубликат этого вопроса, поэтому я не мог ответить на него там. Поэтому я разместил здесь свой ответ.
Malvineous
На этот вопрос уже есть принятый ответ. Он помечен как дубликат, потому что общая причина указана выше. Что случилось бы, если бы у нас был ответ здесь для каждой проблемы с библиотекой, которая не включена?
Лучиан Григоре
6
@LuchianGrigore: эта проблема не характерна для библиотеки, она затрагивает все библиотеки, использующие систему управления пакетами Visual Studio. Я случайно нашел другой вопрос, потому что у нас обоих были проблемы с libpng. У меня также была та же проблема (с тем же решением) для libxml2, libiconv и glew. Этот вопрос касается проблемы с системой управления пакетами Visual Studio, и мой ответ объясняет причину и предлагает обходной путь. Кто-то только что увидел «неразрешенный внешний» и предположил, что это стандартная проблема компоновщика, когда на самом деле это проблема управления пакетами.
Malvineous
34

Предположим, у вас есть большой проект, написанный на c ++, который имеет тысячу файлов .cpp и тысячу файлов .h. И давайте скажем, что проект также зависит от десяти статических библиотек. Допустим, мы находимся на Windows, и мы строим наш проект в Visual Studio 20xx. Когда вы нажимаете Ctrl + F7 Visual Studio, чтобы начать компиляцию всего решения (предположим, у нас есть только один проект в решении)

В чем смысл компиляции?

  • Visual Studio поиск в файл .vcxproj и начинает компилировать каждый файл с расширением .cpp. Порядок компиляции не определен. Так что вы не должны предполагать, что файл main.cpp компилируется первым
  • Если файлы .cpp зависят от дополнительных файлов .h, чтобы найти символы, которые могут или не могут быть определены в файле .cpp
  • Если существует один файл .cpp, в котором компилятор не может найти один символ, ошибка времени компилятора вызывает сообщение Символ x не найден
  • Для каждого файла с расширением .cpp создается объектный файл .o, а также Visual Studio записывает выходные данные в файл с именем ProjectName.Cpp.Clean.txt, который содержит все объектные файлы, которые должны обрабатываться компоновщиком.

Второй этап компиляции выполняется Linker.Linker должен объединить весь объектный файл и, наконец, создать вывод (который может быть исполняемым файлом или библиотекой).

Шаги в Связывание проекта

  • Проанализируйте все объектные файлы и найдите определение, которое было объявлено только в заголовках (например: код одного метода класса, как упомянуто в предыдущих ответах, или событие инициализации статической переменной, которая является членом внутри класса)
  • Если один символ не может быть найден в объектных файлах, он также ищется в «Дополнительные библиотеки». Для добавления новой библиотеки в проект « Свойства конфигурации» -> « Каталоги VC ++» -> « Каталоги библиотек», и здесь вы указали дополнительную папку для поиска библиотек и « Свойства конфигурации» -> Компоновщик -> Вход для указания имени библиотеки. -Если компоновщик не может найти символ, который вы пишете в одном .cpp, он вызывает ошибку компоновщика, которая может звучать как error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)

наблюдение

  1. Как только Линкер найдет один символ, он не будет искать его в других библиотеках.
  2. Порядок связывания библиотек имеет значение .
  3. Если компоновщик находит внешний символ в одной статической библиотеке он включает символ в выходе project.However, если библиотека является общей (динамическим) , он не включает в себя код (символы) на выходе, но Run-Time происшествий может происходить

Как решить эту ошибку

Ошибка времени компиляции:

  • Убедитесь, что вы написали свой синтаксический проект на C ++ правильно.

Ошибка компоновщика

  • Определите все ваши символы, которые вы объявляете в заголовочных файлах
  • Используйте #pragma onceдля разрешения компилятору не включать один заголовок, если он уже был включен в текущий .cpp, который компилируется
  • Убедитесь, что ваша внешняя библиотека не содержит символов, которые могут вступать в конфликт с другими символами, которые вы определили в ваших заголовочных файлах.
  • При использовании шаблона убедитесь, что вы включили определение каждой функции шаблона в заголовочный файл, чтобы позволить компилятору генерировать соответствующий код для любых экземпляров.
eFarzad
источник
Разве ваш ответ не характерен для визуальной студии? В вопросе не указаны инструменты IDE / компилятора, поэтому он делает ваш ответ бесполезным для невизуальной части студии.
Виктор Полевой
Вы правы . Но каждый процесс IDE компиляции / компоновки выполняется немного по-разному. Но файлы обрабатываются одинаково (даже g ++ делает то же самое, когда разбирает флаги ..)
Проблема на самом деле не в IDE, а в ответе на проблемы со связыванием. Проблемы связывания связаны не с IDE, а с компилятором и процессом сборки.
Виктор Полевой
Да. Но процесс сборки / компоновки выполняется в g ++ / Visual Studio (компилятор, предоставленный Microsoft для VS) / Eclipse / Net Beans таким же образом
29

Ошибка в компиляторе / IDE

У меня недавно была эта проблема, и оказалось, что это была ошибка в Visual Studio Express 2013 . Мне пришлось удалить исходный файл из проекта и повторно добавить его, чтобы устранить ошибку.

Действия, если вы считаете, что это может быть ошибка в компиляторе / IDE:

  • Очистите проект (некоторые IDE имеют возможность сделать это, вы также можете сделать это вручную, удалив объектные файлы)
  • Попробуйте начать новый проект, скопировав весь исходный код из исходного.
developerbmw
источник
5
Вера в то, что ваши инструменты сломаны, скорее всего, уведет вас от истинной причины. Просто гораздо более вероятно, что вы допустили ошибку, чем компилятор вызвал вашу проблему. Очистка вашего решения или повторное создание конфигурации сборки может исправить ошибки сборки, но это не означает, что в компиляторе есть ошибка. Ссылка «оказалось, что это была ошибка» не подтверждена Microsoft и не воспроизводима.
JDiMatteo
4
@JDiMatteo На этот вопрос есть 21 ответ, и поэтому значительное количество ответов не будет «вероятным» решением. Если вы отклоните все ответы, которые ниже вашего порога правдоподобия, то эта страница фактически становится бесполезной, так как большинство общих случаев легко обнаружить.
developerbmw
27

Используйте компоновщик, чтобы помочь диагностировать ошибку

Большинство современных линкеров содержат подробный вариант, который печатает в разной степени;

  • Ссылочный вызов (командная строка),
  • Данные о том, какие библиотеки включены в этап ссылки,
  • Расположение библиотек,
  • Используемые пути поиска.

Для gcc и clang; вы обычно добавляете -v -Wl,--verboseили -v -Wl,-vв командную строку. Более подробную информацию можно найти здесь;

Для MSVC /VERBOSE(в частности /VERBOSE:LIB) добавляется в командную строку ссылки.

Найл
источник
26

Связанный .lib файл связан с .dll

Я была такая же проблема. Скажем, у меня есть проекты MyProject и TestProject. Я эффективно связал файл lib для MyProject с TestProject. Однако этот файл lib был создан как библиотека DLL для MyProject. Кроме того, я не содержал исходный код для всех методов в MyProject, но только доступ к точкам входа DLL.

Чтобы решить эту проблему, я построил MyProject как LIB и связал TestProject с этим файлом .lib (скопировал и вставил сгенерированный файл .lib в папку TestProject). Затем я могу снова построить MyProject как DLL. Он компилируется, поскольку библиотека, с которой связан TestProject, содержит код для всех методов в классах в MyProject.

kiriloff
источник
25

Поскольку люди, кажется, обращаются к этому вопросу, когда дело доходит до ошибок компоновщика, я собираюсь добавить это здесь.

Одна из возможных причин ошибок компоновщика в GCC 5.2.0 заключается в том, что теперь по умолчанию выбрана новая библиотека ABI libstdc ++.

Если вы получаете ошибки компоновщика о неопределенных ссылках на символы, которые включают типы в пространстве имен std :: __ cxx11 или теге [abi: cxx11], то это, вероятно, означает, что вы пытаетесь связать вместе объектные файлы, которые были скомпилированы с различными значениями для _GLIBCXX_USE_CXX11_ABI макро. Это обычно происходит при подключении к сторонней библиотеке, которая была скомпилирована с более старой версией GCC. Если сторонняя библиотека не может быть перестроена с новым ABI, вам нужно будет перекомпилировать ваш код со старым ABI.

Так что, если вы внезапно получите ошибки компоновщика при переключении на GCC после 5.1.0, это было бы полезно проверить.

планкалкюль
источник
20

Оболочка вокруг GNU ld, которая не поддерживает скрипты компоновщика

Некоторые файлы .so на самом деле являются сценариями компоновщика GNU ld , например, файл libtbb.so представляет собой текстовый файл ASCII со следующим содержимым:

INPUT (libtbb.so.2)

Некоторые более сложные сборки могут не поддерживать это. Например, если вы включите параметр -v в опциях компилятора, вы увидите, что оболочка mainwin gcc mwdip отбрасывает командные файлы сценария компоновщика в подробный список вывода библиотек для компоновки. Простой способ обойти это - заменить команду ввода сценария компоновщика. вместо этого файл с копией файла (или символической ссылкой), например

cp libtbb.so.2 libtbb.so

Или вы можете заменить аргумент -l на полный путь к .so, например вместо -ltbbdo/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2

JDiMatteo
источник
20

Ваша связь использует библиотеки перед объектными файлами, которые к ним относятся

  • Вы пытаетесь скомпилировать и связать свою программу с помощью цепочки инструментов GCC.
  • Ваша связь указывает все необходимые библиотеки и пути поиска библиотек
  • Если libfooзависит от libbar, то ваша связь правильно ставится libfooраньше libbar.
  • Ваша связь не может с undefined reference to чем - то ошибкой.
  • Но все неопределенные что-то s объявляются в заголовочных файлах, которые у вас есть, #includeи фактически определяются в библиотеках, которые вы связываете.

Примеры есть на C. Они также могут быть C ++

Минимальный пример статической библиотеки, которую вы создали сами

my_lib.c

#include "my_lib.h"
#include <stdio.h>

void hw(void)
{
    puts("Hello World");
}

my_lib.h

#ifndef MY_LIB_H
#define MT_LIB_H

extern void hw(void);

#endif

eg1.c

#include <my_lib.h>

int main()
{
    hw();
    return 0;
}

Вы строите свою статическую библиотеку:

$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o

Вы компилируете свою программу:

$ gcc -I. -c -o eg1.o eg1.c

Вы пытаетесь связать это с libmy_lib.aи терпите неудачу:

$ gcc -o eg1 -L. -lmy_lib eg1.o 
eg1.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

Тот же результат, если вы компилируете и ссылаетесь за один шаг, например:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status

Минимальный пример, включающий общую системную библиотеку, библиотеку сжатия libz

eg2.c

#include <zlib.h>
#include <stdio.h>

int main()
{
    printf("%s\n",zlibVersion());
    return 0;
}

Скомпилируйте вашу программу:

$ gcc -c -o eg2.o eg2.c

Попробуйте связать вашу программу с libzошибкой:

$ gcc -o eg2 -lz eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

То же самое, если вы компилируете и ссылаетесь сразу:

$ gcc -o eg2 -I. -lz eg2.c
/tmp/ccxCiGn7.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
collect2: error: ld returned 1 exit status

И вариант в примере 2, включающий pkg-config:

$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o 
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'

Что ты делаешь не так?

В последовательности объектных файлов и библиотек, которые вы хотите связать для создания своей программы, вы размещаете библиотеки перед объектными файлами, которые к ним относятся. Вам нужно разместить библиотеки после объектных файлов, которые к ним относятся.

Ссылка на пример 1 правильно:

$ gcc -o eg1 eg1.o -L. -lmy_lib

Успех:

$ ./eg1 
Hello World

Ссылка на пример 2 правильно:

$ gcc -o eg2 eg2.o -lz

Успех:

$ ./eg2 
1.2.8

pkg-configПравильно свяжите вариант 2 :

$ gcc -o eg2 eg2.o $(pkg-config --libs zlib) 
$ ./eg2
1.2.8

Объяснение

Чтение не является обязательным с этого момента .

По умолчанию команда связывания, созданная GCC в вашем дистрибутиве, использует файлы в связке слева направо в последовательности командной строки. Когда он обнаруживает, что файл ссылается на что-то и не содержит определения для него, он будет искать определение в файлах далее справа. Если это в конечном счете находит определение, ссылка разрешена. Если какие-либо ссылки остаются неразрешенными в конце, связывание завершается ошибкой: компоновщик не выполняет поиск в обратном направлении.

Сначала пример 1 со статической библиотекойmy_lib.a

Статическая библиотека - это индексированный архив объектных файлов. Когда компоновщик находит -lmy_libв последовательности компоновки и выясняет , что это относится к статической библиотеке ./libmy_lib.a, он хочет знать, нуждается ли ваша программа в каких-либо объектных файлах libmy_lib.a.

В нем есть только объектный файл libmy_lib.a, а my_lib.oв нем определена только одна вещь my_lib.o- функция hw.

Компоновщик решит, что вашей программе нужно, my_lib.oесли и только если он уже знает, на что ссылается ваша программа hw, в одном или нескольких объектных файлах, которые он уже добавил в программу, и что ни один из уже добавленных объектных файлов не содержит определение для hw.

Если это так, то компоновщик извлечет копию my_lib.oиз библиотеки и добавит ее в вашу программу. Затем программа содержит определение hw, поэтому его ссылки на hwкоторые разрешаются .

При попытке связать программу, как:

$ gcc -o eg1 -L. -lmy_lib eg1.o

компоновщик не добавил eg1.o в программу, когда он видит -lmy_lib. Потому что на тот момент его еще не видели eg1.o. Ваша программа еще не делает никаких ссылок на hwнее: она еще не делает никаких ссылок вообще , потому что все ссылки, которые она делает, находятся в eg1.o.

Таким образом, компоновщик не добавляет my_lib.oв программу и не имеет дальнейшего использования libmy_lib.a.

Затем он находит eg1.oи добавляет его в программу. Объектный файл в последовательности связей всегда добавляется в программу. Теперь программа ссылается hwи не содержит определения hw; но в последовательности связей не осталось ничего, что могло бы дать недостающее определение. Ссылка на hwоказывается неразрешенной , и связь не выполняется.

Второй, пример 2 , с общей библиотекойlibz

Общая библиотека не является архивом объектных файлов или чем-то подобным. Это больше похоже на программу , которая не имеет mainфункции и вместо этого предоставляет множество других символов, которые она определяет, так что другие программы могут использовать их во время выполнения.

Многие Linux дистрибутивов сегодня настроить их GCC набор инструментов так , чтобы его язык водители ( gcc, g++, и gfortranт.д.) проинструктировать систему линкера ( ld) , чтобы связать разделяемые библиотеки на в качестве необходимой основе. У вас есть один из этих дистрибутивов.

Это означает, что когда компоновщик находит -lzв последовательности компоновки и выясняет , что это относится к общей библиотеке (скажем) /usr/lib/x86_64-linux-gnu/libz.so, он хочет знать, имеют ли какие-либо ссылки, которые он добавил в вашу программу, которые еще не определены, определения, которые экспортируетсяlibz

Если это так, то компоновщик не будет копировать какие-либо фрагменты libzи добавлять их в вашу программу; вместо этого он просто изменит код вашей программы, чтобы:

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

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

Ваша программа хочет ссылаться только на одну вещь, для которой экспортировано определение libz, а именно на функцию zlibVersion, на которую ссылаются только один раз, в eg2.c. Если компоновщик добавляет эту ссылку в вашу программу, а затем находит определение, экспортированное с помощью libz, ссылка разрешается

Но когда вы пытаетесь связать программу, как:

gcc -o eg2 -lz eg2.o

порядок событий неправильный точно так же, как в примере 1. В тот момент, когда компоновщик обнаруживает -lz, в программе нет ссылок на что-либо: они все находятся внутри eg2.o, что еще не было замечено. Поэтому компоновщик решает, что он бесполезен libz. Когда он достигает eg2.o, добавляет его в программу и затем имеет неопределенную ссылку zlibVersion, последовательность связывания заканчивается; эта ссылка не разрешена, и связь не работает.

Наконец, pkg-configвариант примера 2 имеет теперь очевидное объяснение. После расширения оболочки:

gcc -o eg2 $(pkg-config --libs zlib) eg2.o

будет выглядеть так:

gcc -o eg2 -lz eg2.o

это просто пример 2 снова.

Я могу воспроизвести проблему в примере 1, но не в примере 2

Связь:

gcc -o eg2 -lz eg2.o

работает просто отлично для вас!

(Или: эта связь работала у вас, скажем, на Fedora 23, но не работает на Ubuntu 16.04)

Это связано с тем, что дистрибутив, на котором работает связывание, является одним из тех, которые не настраивают свой набор инструментов GCC для связывания разделяемых библиотек по мере необходимости .

Раньше для unix-подобных систем было нормальным связывать статические и разделяемые библиотеки по разным правилам. Статические библиотеки в последовательности связывания были связаны по мере необходимости, объясненной в примере 1, но совместно используемые библиотеки были связаны безоговорочно.

Такое поведение экономично во время компоновки, поскольку компоновщику не нужно задумываться о том, нужна ли программе общая библиотека: если это общая библиотека, свяжите ее. И большинство библиотек в большинстве связей являются общими библиотеками. Но есть и недостатки:

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

  • Различные правила связывания для статических и разделяемых библиотек могут сбивать с толку неопытных программистов, которые могут не знать, будет ли -lfooв их связывании разрешаться /some/where/libfoo.aили нет /some/where/libfoo.so, и могут все равно не понимать разницу между разделяемыми и статическими библиотеками.

Этот компромисс привел к раскольнической ситуации сегодня. Некоторые дистрибутивы изменили свои правила связывания GCC для разделяемых библиотек, так что принцип по мере необходимости применяется ко всем библиотекам. Некоторые дистрибутивы застряли по-старому.

Почему я все еще получаю эту проблему, даже если я одновременно компилирую и компоноваю?

Если я просто сделаю:

$ gcc -o eg1 -I. -L. -lmy_lib eg1.c

конечно, gcc eg1.cсначала должен скомпилировать , а затем связать полученный объектный файл с libmy_lib.a. Так как же он не может знать, что объектный файл нужен, когда он выполняет связывание?

Поскольку компиляция и компоновка с помощью одной команды не изменяют порядок последовательности компоновки.

Когда вы запустите команду выше, gccвыясните, что вам нужна компиляция + компоновка. Так что за кулисами он генерирует команду компиляции и запускает ее, затем генерирует команду связывания и запускает ее, как если бы вы выполнили две команды:

$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o

Таким образом, связь не может так же , как это делает , если вы действительно запустить эти две команды. Единственное отличие, которое вы замечаете в ошибке, состоит в том, что gcc сгенерировал временный объектный файл в случае компиляции + ссылки, потому что вы не говорите ему использовать eg1.o. Мы видим:

/tmp/ccQk1tvs.o: In function `main'

вместо:

eg1.o: In function `main':

Смотрите также

Порядок, в котором указаны взаимозависимые связанные библиотеки, неверен

Размещение взаимозависимых библиотек в неправильном порядке - это всего лишь один из способов, с помощью которого вы можете получить файлы, которые требуют определений вещей, которые появятся позже в связке, чем файлы, которые предоставляют определения. Размещение библиотек перед объектными файлами, которые ссылаются на них, является еще одним способом сделать ту же ошибку.

Майк Кингхан
источник
18

Шаблоны для друзей ...

Дан фрагмент кода типа шаблона с оператором друга (или функцией);

template <typename T>
class Foo {
    friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};

Объект operator<<объявляется как не шаблонная функция. Для каждого типа, Tиспользуемого с Foo, должен быть не шаблон operator<<. Например, если Foo<int>объявлен тип , то должна быть реализация оператора следующим образом;

std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}

Поскольку это не реализовано, компоновщик не может найти его и приводит к ошибке.

Чтобы исправить это, вы можете объявить оператор шаблона перед Fooтипом, а затем объявить в качестве друга соответствующий экземпляр. Синтаксис немного неудобен, но выглядит следующим образом;

// forward declare the Foo
template <typename>
class Foo;

// forward declare the operator <<
template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&);

template <typename T>
class Foo {
    friend std::ostream& operator<< <>(std::ostream& os, const Foo<T>& a);
    // note the required <>        ^^^^
    // ...
};

template <typename T>
std::ostream& operator<<(std::ostream&, const Foo<T>&)
{
  // ... implement the operator
}

Вышеприведенный код ограничивает дружбу оператора соответствующим экземпляром Foo, то есть operator<< <int>экземпляр ограничен доступом к закрытым членам экземпляра Foo<int>.

Альтернативы включают в себя;

  • Позволяя дружбе распространяться на все экземпляры шаблонов следующим образом;

    template <typename T>
    class Foo {
        template <typename T1>
        friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
        // ...
    };
  • Или, реализация для operator<<может быть встроена внутри определения класса;

    template <typename T>
    class Foo {
        friend std::ostream& operator<<(std::ostream& os, const Foo& a)
        { /*...*/ }
        // ...
    };

Обратите внимание : когда объявление оператора (или функции) появляется только в классе, имя недоступно для «нормального» поиска, только для поиска, зависящего от аргумента, из cppreference ;

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

Дальнейшее чтение о друзьях шаблонов можно найти в cppreference и в C ++ FAQ .

Список кодов, показывающий методы выше .


Как примечание к ошибочному образцу кода; G ++ предупреждает об этом следующим образом

warning: friend declaration 'std::ostream& operator<<(...)' declares a non-template function [-Wnon-template-friend]

note: (if this is not what you intended, make sure the function template has already been declared and add <> after the function name here)

Найл
источник
16

Когда ваши пути включения отличаются

Ошибки компоновщика могут возникать, когда файл заголовка и связанная с ним общая библиотека (файл .lib) не синхронизируются. Позволь мне объяснить.

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

Возможно ли все еще получить ошибку компоновщика, даже если объявление и определение, кажется, совпадают? Да! Они могут выглядеть одинаково в исходном коде, но это действительно зависит от того, что видит компилятор. По сути, вы можете получить такую ​​ситуацию:

// header1.h
typedef int Number;
void foo(Number);

// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically

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

Вы можете спросить, как человек попадает в такую ​​ситуацию? Включите пути конечно! Если при компиляции разделяемой библиотеки путь включения приводит к тому, что header1.hвы в конечном итоге используете ее header2.hв своей программе, вам останется поцарапать заголовок, задаваясь вопросом, что произошло (каламбур предназначен).

Пример того, как это может произойти в реальном мире, объясняется ниже.

Дальнейшая разработка с примером

У меня есть два проекта: graphics.libи main.exe. Оба проекта зависят от common_math.h. Предположим, библиотека экспортирует следующую функцию:

// graphics.lib    
#include "common_math.h" 

void draw(vec3 p) { ... } // vec3 comes from common_math.h

И тогда вы идете дальше и включаете библиотеку в свой собственный проект.

// main.exe
#include "other/common_math.h"
#include "graphics.h"

int main() {
    draw(...);
}

Boom! Вы получаете ошибку компоновщика, и вы не знаете, почему он не работает. Причина в том, что общая библиотека использует разные версии одного и того же include common_math.h(я продемонстрировал здесь, в примере, включив другой путь, но это не всегда так очевидно. Возможно, путь включения отличается в настройках компилятора) ,

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

Отладка компоновщика

DUMPBIN - ваш друг, если вы используете Visual Studio. Я уверен, что другие компиляторы имеют другие подобные инструменты.

Процесс идет так:

  1. Обратите внимание на странное искаженное имя, данное в ошибке компоновщика. (например, draw @ graphics @ XYZ).
  2. Сохраните экспортированные символы из библиотеки в текстовый файл.
  3. Найдите экспортированный символ интереса и обратите внимание, что искаженное имя отличается.
  4. Обратите внимание на то, почему искаженные имена оказались разными. Вы сможете увидеть, что типы параметров различны, даже если они выглядят одинаково в исходном коде.
  5. Причина, почему они разные. В приведенном выше примере они различаются из-за разных включаемых файлов.

[1] Под проектом я подразумеваю набор исходных файлов, которые связаны вместе для создания библиотеки или исполняемого файла.

РЕДАКТИРОВАТЬ 1: переписал первый раздел, чтобы было легче понять. Пожалуйста, прокомментируйте ниже, чтобы сообщить мне, если что-то еще нужно исправить. Спасибо!

fafaro
источник
15

Непоследовательные UNICODEопределения

Сборка Windows UNICODE строится так, чтобы TCHARи т. Д. Определялась как wchar_tи т. Д. Если не строится с помощью UNICODEопределения, как сборка, с TCHARопределением как charи т. Д. Эти UNICODEи _UNICODEопределения влияют на все " T" строковые типы ; LPTSTR, LPCTSTRИ их лосей.

Создание одной библиотеки с UNICODEопределенной и попытка связать ее в проекте, где UNICODEона не определена, приведет к ошибкам компоновщика, поскольку в определении будет несоответствие TCHAR; charпротив wchar_t.

Ошибка обычно включает в себя функцию со значением charили wchar_tпроизводным типом, они также могут включать std::basic_string<>и т. Д. При просмотре уязвимой функции в коде часто встречается ссылка на TCHARи std::basic_string<TCHAR>т. Д. Это контрольный признак того, что код изначально предназначался как для сборки UNICODE, так и для многобайтовых символов (или «узких»). ,

Чтобы исправить это, соберите все необходимые библиотеки и проекты с непротиворечивым определением UNICODE_UNICODE).

  1. Это может быть сделано с любой;

    #define UNICODE
    #define _UNICODE
  2. Или в настройках проекта;

    Свойства проекта> Общие> Проект по умолчанию> Набор символов

  3. Или в командной строке;

    /DUNICODE /D_UNICODE

Альтернатива также применима, если UNICODE не предназначен для использования, убедитесь, что определения не установлены, и / или многосимвольный параметр используется в проектах и ​​применяется последовательно.

Не забывайте также соблюдать согласованность между сборками «Release» и «Debug».

Найл
источник
14

Очистить и восстановить

«Чистая» сборка может удалить «мертвую древесину», которая может быть оставлена ​​на месте из предыдущих сборок, неудачных сборок, неполных сборок и других проблем сборки, связанных с системой сборки.

В общем случае IDE или сборка будут включать в себя некоторую форму «чистой» функции, но это может быть неправильно настроено (например, в файле ручной сборки) или может завершиться ошибкой (например, промежуточные или результирующие двоичные файлы доступны только для чтения).

После завершения «clean» убедитесь, что «clean» завершился успешно и все сгенерированные промежуточные файлы (например, автоматический make-файл) были успешно удалены.

Этот процесс можно рассматривать как последнее средство, но часто это хороший первый шаг ; особенно если недавно был добавлен код, связанный с ошибкой (локально или из исходного репозитория).

Найл
источник
10

Отсутствует "extern" в constобъявлениях / определениях переменных (только C ++)

Для людей, пришедших из C, может быть удивительным, что в C ++ глобальные constпеременные имеют внутреннюю (или статическую) связь. В Си это было не так, поскольку все глобальные переменные неявно extern(то есть, когда staticключевое слово отсутствует).

Пример:

// file1.cpp
const int test = 5;    // in C++ same as "static const int test = 5"
int test2 = 5;

// file2.cpp
extern const int test;
extern int test2;

void foo()
{
 int x = test;   // linker error in C++ , no error in C
 int y = test2;  // no problem
}

правильно будет использовать заголовочный файл и включить его в file2.cpp и file1.cpp

extern const int test;
extern int test2;

В качестве альтернативы можно объявить constпеременную в file1.cpp с явнымextern

Андреас Х.
источник
8

Несмотря на то, что это довольно старые вопросы с несколькими принятыми ответами, я хотел бы поделиться тем, как устранить скрытую ошибку «неопределенная ссылка на».

Разные версии библиотек

Я использовал псевдоним для ссылки std::filesystem::path: файловая система находится в стандартной библиотеке начиная с C ++ 17, но моя программа должна была также компилироваться в C ++ 14, поэтому я решил использовать переменный псевдоним:

#if (defined _GLIBCXX_EXPERIMENTAL_FILESYSTEM) //is the included filesystem library experimental? (C++14 and newer: <experimental/filesystem>)
using path_t = std::experimental::filesystem::path;
#elif (defined _GLIBCXX_FILESYSTEM) //not experimental (C++17 and newer: <filesystem>)
using path_t = std::filesystem::path;
#endif

Допустим, у меня есть три файла: main.cpp, file.h, file.cpp:

  • file.h # include < экспериментальная :: файловая система > и содержит код выше
  • file.cpp , реализация file.h, # включают в " file.h "
  • main.cpp # include < filesystem > и " file.h "

Обратите внимание на различные библиотеки, используемые в main.cpp и file.h. Поскольку main.cpp # include'd « file.h » после < filesystem >, используемая там версия файловой системы была C ++ 17 . Я использовал для компиляции программы с помощью следующих команд:

$ g++ -g -std=c++17 -c main.cpp-> компилирует main.cpp в main.o
$ g++ -g -std=c++17 -c file.cpp-> компилирует file.cpp и file.h в file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs-> связывает main.o и file.o

Таким образом , любая функция , содержащаяся в file.o и используется в main.o , что требуетсяpath_t дал «неопределенная ссылка» ошибки , потому что main.o называют , std::filesystem::pathно file.o к std::experimental::filesystem::path.

разрешение

Чтобы это исправить, мне просто нужно было изменить <экспериментальный :: файловая система> в file.h на <файловая система> .

Stypox
источник
5

При связывании с общими библиотеками убедитесь, что используемые символы не скрыты.

Поведение gcc по умолчанию состоит в том, что все символы видны. Однако, когда единицы перевода построены с опцией -fvisibility=hidden, только функции / символы, помеченные значком, __attribute__ ((visibility ("default")))являются внешними в результирующем общем объекте.

Вы можете проверить, являются ли символы, которые вы ищете, внешними, вызвав:

# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL 

скрытые / локальные символы nmобозначаются строчными символами, например tвместо `T для секции кода:

nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL

Вы также можете использовать nmс опцией, -Cчтобы разобрать имена (если использовался C ++).

Как и в Windows-DLL, можно пометить публичные функции с помощью определения, например, DLL_PUBLICопределенного как:

#define DLL_PUBLIC __attribute__ ((visibility ("default")))

DLL_PUBLIC int my_public_function(){
  ...
}

Что примерно соответствует Windows / MSVC-версии:

#ifdef BUILDING_DLL
    #define DLL_PUBLIC __declspec(dllexport) 
#else
    #define DLL_PUBLIC __declspec(dllimport) 
#endif

Более подробную информацию о видимости можно найти в gcc wiki.


Когда модуль перевода скомпилирован с -fvisibility=hiddenрезультирующими символами, они все еще имеют внешнюю связь (показывается символом верхнего регистра в виде символа nm) и могут без проблем использоваться для внешней связи, если объектные файлы становятся частью статических библиотек. Связь становится локальной, только когда объектные файлы связаны в общей библиотеке.

Чтобы определить, какие символы в объектном файле скрыты, выполните:

>>> objdump -t XXXX.o | grep hidden
0000000000000000 g     F .text  000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g     F .text  000000000000000b .hidden HIDDEN_SYMBOL2
Свинец
источник
Вы должны использовать nm -CDили nm -gCDдля просмотра внешних символов. Также см. Видимость на вики GCC.
jww
2

Разные архитектуры

Вы можете увидеть сообщение как:

library machine type 'x64' conflicts with target machine type 'X86'

В этом случае это означает, что доступные символы для другой архитектуры, чем та, для которой вы компилируете.

В Visual Studio это происходит из-за неправильной «Платформы», и вам нужно либо выбрать подходящую, либо установить правильную версию библиотеки.

В Linux это может быть связано с неправильной папкой библиотеки (используется libвместо, lib64например).

В MacOS есть возможность отправить обе архитектуры в одном файле. Может быть так, что ссылка ожидает, что обе версии будут там, но только одна есть. Это также может быть проблема с неправильной папкой lib/, в lib64которой находится библиотека.

Мэтью Брухер
источник