Що таке невизначена посилання / невирішена помилка зовнішнього символу та як це виправити?


1493

Що таке невизначені посилання / невирішені помилки зовнішнього символу? Які загальні причини і як їх усунути / запобігти?

Не соромтеся редагувати / додавати власні.


@LuchianGrigore "не соромтесь відповісти" Я вважав за краще додати відповідне посилання (IMHO) до своєї основної відповіді, якщо ви хочете дозволити.
πάντα ῥεῖ

19
Це питання є занадто загальним, щоб визнати корисною відповідь. Я не можу повірити, що кількість репутацій 1000+ людей коментує це, не знімаючи питання. Тим часом я бачу багато питань, які є розумними та дуже корисними для мене, які закриті.
Альберт ван дер Хорст

10
@AlbertvanderHorst "Це питання занадто загальне, щоб визнати корисною відповідь" Чи наведені нижче відповіді не корисні?
LF

4
Вони можуть бути корисними, як і багато відповідей на запитання, які позначені як занадто загальні.
Альберт ван дер Хорст

1
Я хотів би бачити мінімально відтворюваний приклад як щось, про що ми запитуємо у більшості нових користувачів, чесно. Я нічого не маю на увазі під цим, це просто - ми не можемо очікувати, що люди будуть дотримуватися правил, яких ми не примушуємо до себе.
Данило

Відповіді:


848

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

Пріоритетність серед правил синтаксису перекладу визначається наступними фазами [див. Виноску] .

  1. Символи файлів фізичного джерела відображаються у визначеному реалізацією порядку до основного набору символів джерела (вводячи нові символи для індикаторів кінцевих рядків), якщо це необхідно. [SNIP]
  2. Кожен екземпляр символу зворотної косої риски (\), що супроводжується символом нового рядка, видаляється, зрощуючи фізичні лінії джерела, утворюючи логічні лінії джерела. [SNIP]
  3. Вихідний файл розкладається на попередньо оброблювані маркери (2.5) та послідовності символів пробілів (включаючи коментарі). [SNIP]
  4. Виконуються директиви попередньої обробки, розгортаються виклики макросів та виконуються _Pragma унарні вирази оператора. [SNIP]
  5. Кожен член набору символів джерела в буквальному або строковому літералі, а також кожна послідовність відстеження та ім'я універсального символу в літеральному символі або неочищеному літеральному рядку перетворюються у відповідний член набору символів виконання; [SNIP]
  6. Суміжні рядкові літеральні лексеми об'єднані.
  7. Символи білого простору, що розділяють лексеми, вже не є значущими. Кожен маркер попередньої обробки перетворюється в маркер. (2.7). Отримані лексеми синтаксично та семантично аналізуються та перекладаються як одиниця перекладу. [SNIP]
  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
Девід Драйсдейл написав чудову статтю про те, як працюють лінкери: Посібник для початківців по Linkers . З огляду на тему цього питання, я вважав, що це може виявитися корисним.
Пресакко

@TankorSmash Використовувати gcc? MinGW, щоб бути більш точним.
doug65536

1
@luchian, було б добре, якщо ви додасте правильну, виправляючи вищезазначені помилки
Phalgun

177

Учасники класу:

Чистий 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членів даних.


Просто подумав, що ти можеш хотіти наголосити, що робити і те, і інше можливо, а дтор насправді не є винятком. (це не очевидно з вашого формулювання на перший погляд.)
Дедуплікатор

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

Так що ще раз повторити, порядок РОБИТЬ справа!


Цікавий факт, що в моєму випадку у мене був один об’єктний файл, який залежить від спільної бібліотеки. Мені довелося змінити 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

Linker бачить експортовані символи та посилається на них. Тепер ми намагаємось відментувати рядки в 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 --symbols src1.o
8: 0000000000000000     4 OBJECT  GLOBAL DEFAULT    3 local_var_name

тепер local_var_name має прив’язати 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();
}

Еквівалентно, замість того, щоб визначатись у програмі С, функція (або змінна) void foo()була визначена в C ++, але із зв'язком C:

extern "C" void foo();

і ви намагаєтеся використовувати його в програмі C ++ із зв'язком на C ++.

Якщо у файл заголовка включена ціла бібліотека (і була складена як код C); включити потрібно буде наступним чином;

extern "C" {
    #include "cheader.h"
}

5
Або, навпаки, якщо ви розробляєте бібліотеку С, хорошим правилом є захист файлів заголовків, оточуючи всі експортовані декларації за допомогою ( #ifdef __cplusplus [\n] extern"C" { [\n] #endifта є справжнім поверненням перевезення, але я не можу це правильно написати в коментарі). #ifdef __cplusplus [\n] } [\n] #endif[\n]
Bentoy13

Як і у вищенаведеному коментарі, розділ "Створення змішаних мов" тут допоміг: oracle.com/technetwork/articles/servers-storage-dev/…
zanbri

Дійсно мені допомогли! Це був мій випадок, коли я шукав це питання
knightofhorizon

Це також може статися, якщо випадково включити звичайний файл заголовка C ++, оточений зовнішньою 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 знайдено.


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 тощо) розглядається як одиниця перекладу, компілятор компілює по одній одиниці і генерує один об’єктний файл (.obj) для поточного блоку перекладу. (Зверніть увагу, що кожен файл заголовка, що міститься у цьому вихідному файлі, буде попередньо оброблений і вважатиметься частиною цього блоку перекладу) Все, що знаходиться в одиниці перекладу, вважається внутрішнім, все інше вважається зовнішнім. У C ++, ви можете посилатися на зовнішній символ, використовуючи ключові слова , як extern, __declspec (dllimport)і так далі.

C. Що таке "рішення"? Розв’язання - термін, що пов'язує час. Під час зв’язування час, лінкер намагається знайти зовнішнє визначення кожного символу в об'єктних файлах, які не можуть знайти його визначення внутрішньо. Обсяг цього процесу пошуку, включаючи:

  • Усі файли об'єктів, що генеруються за час компіляції
  • Усі бібліотеки (.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
Дякую за систематичне пояснення. Я міг зрозуміти інші відповіді тут, прочитавши цю.
показName

55

Реалізації шаблону не відображаються.

Неспеціалізовані шаблони повинні мати свої визначення, видимі для всіх одиниць перекладу, які ними користуються. Це означає, що ви не можете розділити визначення шаблону на файл реалізації. Якщо вам потрібно відокремити реалізацію, звичайним вирішенням є наявність 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 Application до такої функції точки входу (як зазначено у відсутній посилання вище) замість загальновживаної int main(int argc, char** argv);підпису.

Якщо ваш IDE підтримує проекти Plain Console, ви можете обрати цей тип проекту, а не проект програми Windows.


Ось case1 і case2, які детальніше розглядаються з реальної проблеми світу .


2
Не можу не вказати на це питання і на те, що це найчастіше спричинено відсутністю основної функції, ніж відсутністю WinMain. Дійсні програми C ++ потребують main.
chris

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 року, і в цьому випадку воно спрацювало.


1
Це здається занадто специфічним - можливо, нова тема буде кращим місцем для цієї відповіді.
Лучіан Григоре

3
@LuchianGrigore: Я хотів опублікувати тут, оскільки саме це питання було саме цим питанням, але воно було позначене як дублікат цього питання, тому я не міг відповісти на нього. Тому я замість цього розмістив свою відповідь.
Malvineous

На це питання вже є прийнята відповідь. Позначено як дублікат, оскільки загальна причина перерахована вище. Що було б, якби у нас тут була відповідь на кожну проблему з бібліотекою, яка не включена?
Лучіан Григоре

6
@LuchianGrigore: Ця проблема не характерна для бібліотеки, вона стосується всіх бібліотек, що використовують систему управління пакетами Visual Studio. Щойно я знайшов інше питання, тому що у нас обох були проблеми з libpng. У мене також була така ж проблема (з тим же рішенням) для libxml2, libiconv та glow. Це запитання стосується проблеми системи управління пакунками Visual Studio, і моя відповідь пояснює причину та забезпечує вирішення проблеми. Хтось просто побачив "невирішені зовнішні" і припустив, що це стандартна проблема з лінкером, коли це насправді проблема управління пакетом.
Мальвіноз

34

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

У чому сенс компіляції?

  • Visual Studio шукає файл .vcxproj і починає компілювати кожен файл, який має розширення .cpp. Порядок компіляції не визначений. Отже, ви не повинні вважати, що файл main.cpp складається першим
  • Якщо файли .cpp залежать від додаткових .h-файлів, щоб знайти символи, які можуть бути, а можуть і не бути визначені у файлі .cpp
  • Якщо існує один .cpp-файл, у якому компілятор не зміг знайти одного символу, помилка часу компілятора піднімає повідомлення. Symbol x не вдалося знайти
  • Для кожного файлу з розширенням .cpp генерується об’єктний файл .o, а також Visual Studio записує висновок у файл під назвою ProjectName.Cpp.Clean.txt, який містить усі файли об'єктів, які повинні бути оброблені лінкером.

Другий крок компіляції виконується Linker.Linker повинен об'єднати весь об'єктний файл і нарешті створити вихід (який може бути виконуваним файлом або бібліотекою)

Кроки у зв’язуванні проекту

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

Спостереження

  1. Як тільки Linker знайде один символ, він не шукає його в інших бібліотеках
  2. Порядок зв’язку бібліотек має значення .
  3. Якщо Linker знайде зовнішній символ у одній статичній бібліотеці, він включає цей символ у висновок проекту. Однак, якщо бібліотека є спільною (динамічною), він не включає код (символи) у вихідний, але час запуску може збій трапляються

Як вирішити подібну помилку

Помилка часу компілятора:

  • Переконайтеся, що ви правильно написали свій синтаксичний проект c ++.

Помилка часу Linker

  • Визначте весь свій символ, який ви оголосите у файлах заголовка
  • Використовуйте #pragma onceдля дозволу компілятору не включати жодного заголовка, якщо він уже включений у поточний .cpp, який компілюється
  • Переконайтеся, що у вашій зовнішній бібліотеці немає символів, які можуть вступати в конфлікт з іншими символами, визначеними у файлах заголовка
  • Коли ви використовуєте шаблон, щоб переконатися, що ви включите визначення кожної функції шаблону у файл заголовка, щоб дозволити компілятору генерувати відповідний код для будь-яких екземплярів.

Чи не ваша відповідь є специфічною для візуальної студії? Питання не вказує жодних інструментів IDE / компілятора, тому він робить вашу відповідь марною для невізуальної студійної частини.
Віктор Полевий

Ти правий . Але кожен процес компіляції / посилання IDE робиться дещо по-різному. Але файли обробляються точно так само (навіть g ++ роблять те ж саме, коли вони аналізують прапори ..)

Проблема полягає не в IDE, а у відповіді на посилання на проблеми. Проблеми з зв'язуванням пов'язані не з IDE, а з процесом компіляції та складання.
Віктор Полевий

Так. Але процес збирання / зв’язування виконується в g ++ / Visual Studio (компілятор, який надає Microsoft для VS) / Eclipse / Net Beans так само

29

Помилка в компіляторі / IDE

Нещодавно у мене була ця проблема, і виявилося, що це помилка у Visual Studio Express 2013 . Мені довелося видалити вихідний файл із проекту та повторно додати його, щоб подолати помилку.

Кроки, щоб спробувати, якщо ви вважаєте, що це може бути помилка в компіляторі / IDE:

  • Очистіть проект (у деяких IDE є можливість це зробити, ви також можете зробити це вручну, видаливши об’єктні файли)
  • Спробуйте запустити новий проект, скопіювавши весь вихідний код з оригіналу.

5
Вважаючи, що ваші інструменти зламані, швидше за все, це відхилить вас від справжньої причини. Просто набагато більше шансів на те, що ви допустили помилку, ніж компілятор спричинив вашу проблему. Очищення рішення або повторне створення конфігурації збірки може виправити помилки збирання, але це не означає, що в компіляторі є помилка. Пов'язаний "виявилося, що це помилка" не підтверджений Microsoft і не відтворюється.
JDiMatteo

4
@JDiMatteo На це питання є 21 відповідь, і тому значна кількість відповідей не є "ймовірним" рішенням. Якщо ви відхилите всі відповіді, які не перевищують ваш поріг, тоді ця сторінка фактично стає марною, оскільки більшість поширених випадків у будь-якому випадку легко помічаються.
розробник

27

Використовуйте лінкер, щоб допомогти діагностувати помилку

Більшість сучасних посилання включають багатослівний варіант, який друкується в різній мірі;

  • Виклик посилання (командний рядок),
  • Дані про те, які бібліотеки включені в етап посилання,
  • Розташування бібліотек,
  • Використовувані шляхи пошуку.

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

Для MSVC до командного рядка посилання додається /VERBOSE(зокрема /VERBOSE:LIB).


26

Зв'язаний файл .lib пов'язаний з .dll

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

Щоб вирішити проблему, я створив MyProject як LIB і зв’язав TestProject з цим .lib-файлом (я копіюю вставку згенерованого .lib-файлу у папку TestProject). Потім я можу створити MyProject заново як DLL. Він компілюється, оскільки вкладка, з якою пов'язаний TestProject, містить код для всіх методів у класах у MyProject.


25

Оскільки люди, здається, спрямовані на це питання, коли мова заходить про помилки лінкера, я збираюся додати це тут.

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

Якщо ви отримуєте помилки в посиланнях щодо невизначених посилань на символи, що включають типи в просторі імен std :: __ cxx11 або тезі [abi: cxx11], це, ймовірно, вказує на те, що ви намагаєтеся зв’язати разом об’єктні файли, складені з різними значеннями для _GLIBCXX_USE_CXX11_ABI макрос. Це зазвичай трапляється при посиланні на сторонні бібліотеки, які були складені зі старшою версією GCC. Якщо сторонню бібліотеку неможливо відновити з новим ABI, вам потрібно буде перекомпілювати свій код зі старим ABI.

Тож якщо у вас несподівано з’являються помилки лінкера при переході на GCC після 5.1.0, це варто перевірити.


20

Обгортка навколо GNU ld, яка не підтримує сценарії посилання

Деякі файли .so фактично є сценаріями посилання LD GNU , наприклад, файл 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


20

Ваше посилання споживає бібліотеки перед файлами об'єктів, які посилаються на них

  • Ви намагаєтеся скласти та пов’язати свою програму з ланцюжком інструментів GCC.
  • Ваш посилання вказує всі необхідні бібліотеки та шляхи пошуку бібліотеки
  • Якщо від цього libfooзалежить libbar, то ваш зв'язок правильно ставиться libfooранішеlibbar .
  • У вашому зв’язку щось не вдаєтьсяundefined reference to помилкою.
  • Але все невизначене щось s задекларовано у файлах заголовка, який у вас є #included, і насправді визначено в бібліотеках, до яких ви посилаєтеся.

Приклади є в 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

наприклад2.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, але спільні бібліотеки були пов'язані безумовно.

Така поведінка є економічною в режимі linktime, оскільки лінкер не повинен розмірковувати, чи потрібна програмі спільна бібліотека: якщо це спільна бібліотека, зв’яжіть її. І більшість бібліотек у більшості посилань є спільними бібліотеками. Але є і недоліки:

  • Це неекономічно під час виконання , оскільки може спричинити завантаження спільних бібліотек разом із програмою, навіть якщо вони не потребують.

  • Різні правила зв’язку для статичних і спільних бібліотек можуть бентежити недосвідчених програмістів, які, можливо, не знають, чи буде -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<<can може бути вбудована всередині визначення класу;

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

Бум! Ви отримуєте помилку лінкера, і ви не маєте поняття, чому це не працює. Причина полягає в тому, що загальна бібліотека використовує різні версії того ж включати common_math.h(я зробив це очевидним тут у прикладі, включивши інший шлях, але це не завжди може бути таким очевидним. Можливо шлях включення відрізняється в налаштуваннях компілятора) .

Зауважте, у цьому прикладі лінкер сказав вам, що його не вдалося знайти draw(), якщо ви насправді знаєте, що це, очевидно, експортується бібліотекою. Ви могли годинами чесати голову, цікавлячись, що пішло не так. Річ у тім, що лінкер бачить інший підпис, оскільки типи параметрів трохи відрізняються. У прикладі vec3є різний тип в обох проектах, що стосується компілятора. Це може статися, оскільки вони походять з двох дещо різних файлів, що включають (можливо, такі файли включають дві різні версії бібліотеки).

Налагодження лінкера

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

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

  1. Зверніть увагу на дивну наздоженену назву, вказану в помилці лінкера. (наприклад, малювати @ графіку @ XYZ).
  2. Скиньте експортовані символи з бібліотеки у текстовий файл.
  3. Шукайте експортований символ, який цікавить, і зауважте, що ім'я зі зловмисниками відрізняється.
  4. Зверніть увагу, чому зловмисні імена закінчилися різними. Ви зможете побачити, що типи параметрів різні, хоча вони виглядають однаково у вихідному коді.
  5. Причина, чому вони різні. У наведеному вище прикладі вони різні, оскільки різні файли включають.

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

EDIT 1: Перепишіть перший розділ, щоб його було легше зрозуміти. Будь ласка, прокоментуйте нижче, щоб повідомити мені, чи потрібно ще щось виправити. Дякую!


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 не призначений для використання, переконайтеся, що значення не встановлені, та / або багатофункціональний параметр використовується в проектах і послідовно застосовується.

Не забудьте бути послідовними і між складами "Випуск" та "Налагодження".


14

Почистіть і відновіть

"Очищення" збірки може видалити "мертву деревину", яка може залишитися валятися від попередніх збірок, невдалих збірок, неповних збірок та інших проблем, пов’язаних із системою побудови.

Взагалі IDE або збірка включатиме певну форму функції "чистого", але це може бути неправильно налаштовано (наприклад, в ручному makefile) або може вийти з ладу (наприклад, проміжні або результуючі бінарні файли доступні лише для читання).

Після завершення "чистого" переконайтеся, що "чистий" вдався, і всі генеровані проміжні файли (наприклад, автоматизований файл файлів) були успішно видалені.

Цей процес можна розглядати як остаточний засіб, але часто є хорошим першим кроком ; особливо, якщо нещодавно було додано код, пов'язаний з помилкою (локально або з вихідного сховища).


10

Відсутня "екстерн" у constзмінних декларацій / визначень (лише C ++)

Для людей, що походять із С, може бути несподіванкою, що у C ++ глобальні constзмінні мають внутрішню (або статичну) зв'язок. У C це було не так, оскільки всі глобальні змінні неявно 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's < експериментальна :: файлова система > і містить код вище
  • file.cpp , реалізація file.h, # include " file.h "
  • main.cpp # include < файлова система > і " file.h "

Зверніть увагу на різні бібліотеки, які використовуються в main.cpp та file.h. Так як main.cpp # include'd « file.h » після < файлової системи >, версія файлової системи використовується там було С ++ 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але файл.o на std::experimental::filesystem::path.

Дозвіл

Щоб виправити це, мені просто потрібно було змінити <експериментальний :: файлова система> у file.h на <filesystem> .


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-dlls, можна було б позначити загальнодоступні функції дефінітом, наприклад 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.


Коли підрозділ перекладу компілюється з -fvisibility=hiddenотриманими символами, все ще мають зовнішню зв'язок (показано з типом верхнього регістру типу by 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папкою, де вибрано бібліотеку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.