Що таке невизначені посилання / невирішені помилки зовнішнього символу? Які загальні причини і як їх усунути / запобігти?
Не соромтеся редагувати / додавати власні.
Що таке невизначені посилання / невирішені помилки зовнішнього символу? Які загальні причини і як їх усунути / запобігти?
Не соромтеся редагувати / додавати власні.
Відповіді:
Компіляція програми C ++ відбувається в кілька етапів, як зазначено в 2.2 (кредити Кіту Томпсону для довідки) :
Пріоритетність серед правил синтаксису перекладу визначається наступними фазами [див. Виноску] .
- Символи файлів фізичного джерела відображаються у визначеному реалізацією порядку до основного набору символів джерела (вводячи нові символи для індикаторів кінцевих рядків), якщо це необхідно. [SNIP]
- Кожен екземпляр символу зворотної косої риски (\), що супроводжується символом нового рядка, видаляється, зрощуючи фізичні лінії джерела, утворюючи логічні лінії джерела. [SNIP]
- Вихідний файл розкладається на попередньо оброблювані маркери (2.5) та послідовності символів пробілів (включаючи коментарі). [SNIP]
- Виконуються директиви попередньої обробки, розгортаються виклики макросів та виконуються _Pragma унарні вирази оператора. [SNIP]
- Кожен член набору символів джерела в буквальному або строковому літералі, а також кожна послідовність відстеження та ім'я універсального символу в літеральному символі або неочищеному літеральному рядку перетворюються у відповідний член набору символів виконання; [SNIP]
- Суміжні рядкові літеральні лексеми об'єднані.
- Символи білого простору, що розділяють лексеми, вже не є значущими. Кожен маркер попередньої обробки перетворюється в маркер. (2.7). Отримані лексеми синтаксично та семантично аналізуються та перекладаються як одиниця перекладу. [SNIP]
- Перекладені одиниці перекладу та одиниці інстанції об'єднуються так: [SNIP]
- Усі зовнішні посилання суб'єктів господарювання вирішені. Компоненти бібліотеки пов'язані для задоволення зовнішніх посилань на сутності, не визначені в поточному перекладі. Весь такий вихід перекладача збирається у зображення програми, яке містить інформацію, необхідну для виконання у його середовищі виконання. (наголос мій)
[виноска] Реалізація повинна вести себе так, ніби трапляються ці окремі фази, хоча на практиці різні фази можуть бути складені разом.
Зазначені помилки трапляються на цьому останньому етапі компіляції, який найчастіше називають посиланням. Це в основному означає, що ви зібрали купу файлів реалізації в об’єктні файли або бібліотеки, і тепер ви хочете змусити їх працювати разом.
Скажіть, ви визначили символ 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
До поширених причин можна віднести:
#pragma
(Microsoft Visual Studio)UNICODE
визначенняvirtual
деструктор потребує реалізації.Оголошення деструктора чистим все ж вимагає визначити його (на відміну від звичайної функції):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
Це відбувається тому, що деструктори базового класу викликаються, коли об’єкт знищується неявно, тому потрібне визначення.
virtual
методи повинні бути реалізовані або визначені як чисті.Це схоже на неметоди, що не virtual
мають визначення, з додатковим аргументом, що чисте оголошення створює манекен 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
членів даних.
Зазвичай кожен блок перекладу генерує об’єктний файл, який містить визначення символів, визначених у цьому блоці перекладу. Щоб використовувати ці символи, ви повинні зв’язати ці файли об'єктів.
У розділі 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 розміщує інформацію у вікні внизу кожної функції у розділі під назвою "Бібліотека".
gcc main.c
замість цього gcc main.c other.c
(що початківці часто роблять перед тим, як їхні проекти набувають настільки великих розмірів, що створюють файли .o).
Типовою декларацією змінної є
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)
Інші приклади невідповідностей включають
Повідомлення про помилку компілятора часто дає вам повне оголошення змінної або функції, яка була оголошена, але ніколи не визначена. Порівняйте його з визначенням, яке ви надали. Переконайтесь, що кожна деталь відповідає.
#includes
не доданому до вихідного каталогу, також підпадають під категорію відсутніх визначень.
Порядок з'єднання бібліотек має значення, якщо бібліотеки залежать одна від одної. Загалом, якщо бібліотека A
залежить від бібліотеки B
, то libA
ОБОВ'ЯЗКОВО з'являтися раніше libB
у прапорах лінкера.
Наприклад:
// B.h
#ifndef B_H
#define B_H
struct B {
B(int);
int x;
};
#endif
// B.cpp
#include "B.h"
B::B(int xx) : x(xx) {}
// A.h
#include "B.h"
struct A {
A(int x);
B b;
};
// A.cpp
#include "A.h"
A::A(int x) : b(x) {}
// main.cpp
#include "A.h"
int main() {
A a(5);
return 0;
};
Створіть бібліотеки:
$ g++ -c A.cpp
$ g++ -c B.cpp
$ ar rvs libA.a A.o
ar: creating libA.a
a - A.o
$ ar rvs libB.a B.o
ar: creating libB.a
a - B.o
Збірка:
$ g++ main.cpp -L. -lB -lA
./libA.a(A.o): In function `A::A(int)':
A.cpp:(.text+0x1c): undefined reference to `B::B(int)'
collect2: error: ld returned 1 exit status
$ g++ main.cpp -L. -lA -lB
$ ./a.out
Так що ще раз повторити, порядок РОБИТЬ справа!
що таке "невизначений довідник / невирішений зовнішній символ"
Я спробую пояснити, що таке "невизначений довідник / невирішений зовнішній символ".
Примітка: я використовую g ++ та Linux, і всі приклади для цього
Наприклад, у нас є якийсь код
// src1.cpp
void print();
static int local_var_name; // 'static' makes variable not visible for other modules
int global_var_name = 123;
int main()
{
print();
return 0;
}
і
// src2.cpp
extern "C" int printf (const char*, ...);
extern int global_var_name;
//extern int local_var_name;
void print ()
{
// printf("%d%d\n", global_var_name, local_var_name);
printf("%d\n", global_var_name);
}
Створіть об’єктні файли
$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
Після фази асемблера у нас є об'єктний файл, який містить будь-які символи для експорту. Подивіться на символи
$ readelf --symbols src1.o
Num: Value Size Type Bind Vis Ndx Name
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 4 _ZL14local_var_name # [1]
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 global_var_name # [2]
Я відхилив деякі рядки з виводу, тому що вони не мають значення
Отже, ми бачимо, як слід експортувати символи.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp нічого не експортує, і ми не бачили його символів
Пов’яжіть наші об’єктні файли
$ g++ src1.o src2.o -o prog
і запустити його
$ ./prog
123
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
ок, ми це зламаємо :)
Отже, як результат - "невизначена посилання / невирішена помилка зовнішнього символу" трапляється, коли лінкер не може знайти глобальні символи у файлах об'єкта.
Функція (або змінна) 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"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
та є справжнім поверненням перевезення, але я не можу це правильно написати в коментарі). #ifdef __cplusplus [\n] } [\n] #endif
[\n]
extern "C" { #include <myCppHeader.h> }
.
Якщо все інше не вдалося, перекомпілюйте.
Нещодавно мені вдалося позбутися невирішеної зовнішньої помилки в Visual Studio 2012, просто перекомпілювавши файл, що порушує право. Коли я відновлював, помилка зникла.
Зазвичай це відбувається, коли дві (або більше) бібліотек мають циклічну залежність. Бібліотека A намагається використовувати символи в B.lib, а бібліотека B намагається використовувати символи від A.lib. Немає для початку. При спробі скласти A, крок посилання буде невдалим, оскільки він не може знайти B.lib. A.lib буде сформовано, але не буде dll. Потім ви компілюєте B, який буде успішним і генерує B.lib. Перекомпіляція A тепер працюватиме, тому що B.lib знайдено.
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
{
};
visibility
та Windows .def
, оскільки вони також впливають на назву та наявність символу.
.def
файли у віках. Не соромтесь додати відповідь або відредагувати цю.
Це одне з найбільш заплутаних повідомлень про помилки, які кожен програміст VC ++ бачив знову і знову. Давайте спочатку зробимо ясність.
А. Що таке символ? Словом, символ - це ім'я. Це може бути ім'я змінної, ім'я функції, ім'я класу, ім'я typedef або будь-що, крім тих імен та знаків, які належать до мови C ++. Це визначений користувачем або введений бібліотекою залежностей (визначений іншим користувачем).
B. Що таке зовнішнє?
У VC ++ кожен вихідний файл (.cpp, .c тощо) розглядається як одиниця перекладу, компілятор компілює по одній одиниці і генерує один об’єктний файл (.obj) для поточного блоку перекладу. (Зверніть увагу, що кожен файл заголовка, що міститься у цьому вихідному файлі, буде попередньо оброблений і вважатиметься частиною цього блоку перекладу) Все, що знаходиться в одиниці перекладу, вважається внутрішнім, все інше вважається зовнішнім. У C ++, ви можете посилатися на зовнішній символ, використовуючи ключові слова , як extern
, __declspec (dllimport)
і так далі.
C. Що таке "рішення"? Розв’язання - термін, що пов'язує час. Під час зв’язування час, лінкер намагається знайти зовнішнє визначення кожного символу в об'єктних файлах, які не можуть знайти його визначення внутрішньо. Обсяг цього процесу пошуку, включаючи:
Цей процес пошуку називається вирішенням.
D. Нарешті, чому невирішений зовнішній символ? Якщо лінкер не може знайти зовнішнє визначення символу, який не має внутрішнього визначення, він повідомляє про невирішену помилку зовнішнього символу.
E. Можливі причини LNK2019 : Невирішена помилка зовнішнього символу. Ми вже знаємо, що ця помилка пов’язана з тим, що лінкер не зміг визначити визначення зовнішніх символів, можливі причини можна класифікувати як:
Наприклад, якщо у нас функція під назвою foo визначена в a.cpp:
int foo()
{
return 0;
}
У b.cpp ми хочемо викликати функцію foo, тому додаємо
void foo();
оголосити функцію foo () та викликати її в іншому тілі функції, скажіть bar()
:
void bar()
{
foo();
}
Тепер, коли ви будуєте цей код, ви отримаєте помилку LNK2019, яка скаржиться на те, що foo - це невирішений символ. У цьому випадку ми знаємо, що foo () має своє визначення у a.cpp, але відрізняється від того, яке ми викликаємо (різне повернене значення). Це той випадок, що існує визначення.
Якщо ми хочемо викликати деякі функції в бібліотеці, але імпортна бібліотека не додається до додаткового списку залежностей (встановлений з Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
:) вашого налаштування проекту. Тепер лінкер повідомить про LNK2019, оскільки визначення не існує в поточній області пошуку.
Неспеціалізовані шаблони повинні мати свої визначення, видимі для всіх одиниць перекладу, які ними користуються. Це означає, що ви не можете розділити визначення шаблону на файл реалізації. Якщо вам потрібно відокремити реалізацію, звичайним вирішенням є наявність 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
у файл заголовка або в якесь місце, видиме для блоку перекладу, який його використовує.
Спеціалізовані шаблони можуть бути реалізовані у файлі реалізації, і імплементація не повинна бути видимою, але спеціалізація повинна бути попередньо оголошена.
Для подальшого пояснення та іншого можливого рішення (явної інстанції) див. Це питання та відповідь .
неозначена посилання на WinMain@16
або подібну «незвичну» main()
посилання на точку входу (особливо длявізуальна студія).
Можливо, ви пропустили обрати правильний тип проекту з вашим фактичним IDE. IDE може захотіти прив’язати, наприклад, програми Windows Application до такої функції точки входу (як зазначено у відсутній посилання вище) замість загальновживаної int main(int argc, char** argv);
підпису.
Якщо ваш IDE підтримує проекти Plain Console, ви можете обрати цей тип проекту, а не проект програми Windows.
Ось case1 і case2, які детальніше розглядаються з реальної проблеми світу .
WinMain
. Дійсні програми C ++ потребують main
.
Пакет 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 року, і в цьому випадку воно спрацювало.
Припустимо, у вас великий проект, написаний на c ++, який містить тисячу файлів .cpp та тисячу файлів .h. Скажімо, проект також залежить від десяти статичних бібліотек. Скажімо, ми перебуваємо в Windows, і ми будуємо наш проект у Visual Studio 20xx. Коли ви натиснете Ctrl + F7 Visual Studio, щоб почати компілювати все рішення (припустимо, у нас є лише один проект у рішенні)
У чому сенс компіляції?
Другий крок компіляції виконується Linker.Linker повинен об'єднати весь об'єктний файл і нарешті створити вихід (який може бути виконуваним файлом або бібліотекою)
Кроки у зв’язуванні проекту
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Спостереження
Як вирішити подібну помилку
Помилка часу компілятора:
Помилка часу Linker
#pragma once
для дозволу компілятору не включати жодного заголовка, якщо він уже включений у поточний .cpp, який компілюєтьсяНещодавно у мене була ця проблема, і виявилося, що це помилка у Visual Studio Express 2013 . Мені довелося видалити вихідний файл із проекту та повторно додати його, щоб подолати помилку.
Кроки, щоб спробувати, якщо ви вважаєте, що це може бути помилка в компіляторі / IDE:
Більшість сучасних посилання включають багатослівний варіант, який друкується в різній мірі;
Для gcc і clang; ти зазвичай додаєш -v -Wl,--verbose
або -v -Wl,-v
до командного рядка. Більш детальну інформацію можна знайти тут;
Для MSVC до командного рядка посилання додається /VERBOSE
(зокрема /VERBOSE:LIB
).
/VERBOSE
компонування .Зв'язаний файл .lib пов'язаний з .dll
У мене було те саме питання. Скажіть, у мене є проекти MyProject і TestProject. Я ефективно зв’язав файл lib для MyProject з TestProject. Однак цей файл ліб був створений у міру створення DLL для MyProject. Крім того, я не містив вихідний код для всіх методів у MyProject, а лише доступ до точок входу DLL.
Щоб вирішити проблему, я створив MyProject як LIB і зв’язав TestProject з цим .lib-файлом (я копіюю вставку згенерованого .lib-файлу у папку TestProject). Потім я можу створити MyProject заново як DLL. Він компілюється, оскільки вкладка, з якою пов'язаний TestProject, містить код для всіх методів у класах у MyProject.
Оскільки люди, здається, спрямовані на це питання, коли мова заходить про помилки лінкера, я збираюся додати це тут.
Однією з можливих причин помилок лінкера з GCC 5.2.0 є те, що нова бібліотека libstdc ++ ABI тепер обрана за замовчуванням.
Якщо ви отримуєте помилки в посиланнях щодо невизначених посилань на символи, що включають типи в просторі імен std :: __ cxx11 або тезі [abi: cxx11], це, ймовірно, вказує на те, що ви намагаєтеся зв’язати разом об’єктні файли, складені з різними значеннями для _GLIBCXX_USE_CXX11_ABI макрос. Це зазвичай трапляється при посиланні на сторонні бібліотеки, які були складені зі старшою версією GCC. Якщо сторонню бібліотеку неможливо відновити з новим ABI, вам потрібно буде перекомпілювати свій код зі старим ABI.
Тож якщо у вас несподівано з’являються помилки лінкера при переході на GCC після 5.1.0, це варто перевірити.
Обгортка навколо GNU ld, яка не підтримує сценарії посилання
Деякі файли .so фактично є сценаріями посилання LD GNU , наприклад, файл libtbb.so - це текстовий файл ASCII з цим вмістом:
INPUT (libtbb.so.2)
Деякі складніші конструкції можуть не підтримувати це. Наприклад, якщо ви включите -v у параметри компілятора, ви можете бачити, що mainwin gcc обгортка mwdip відкидає файли команд скриптів у списку випуску багатослівних бібліотек, до яких слід посилатися. файл замість нього (або символьне посилання), наприклад, файл
cp libtbb.so.2 libtbb.so
Або ви можете замінити аргумент -l на повний шлях .so, наприклад замість -ltbb
do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
залежить libbar
, то ваш зв'язок правильно ставиться libfoo
ранішеlibbar
.undefined reference to
помилкою.#include
d, і насправді визначено в бібліотеках, до яких ви посилаєтеся.Приклади є в 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.
Зв'язок:
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':
Порядок, у якому вказані взаємозалежні пов'язані бібліотеки, є неправильним
Розміщення взаємозалежних бібліотек у неправильному порядку - це лише один із способів отримання файлів, які потребують визначення речей, що надходять пізніше у зв’язку, ніж файли, що надають визначення. Поставлення бібліотек перед файлами об'єктів, які посилаються на них, - це ще один спосіб помилки.
Дано фрагмент коду типу шаблону з другом оператором (або функцією);
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)
Помилки лінкера можуть статися, коли файл заголовка та пов'язана з ним спільна бібліотека (.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] Під проектом я маю на увазі набір вихідних файлів, пов'язаних між собою, щоб створити або бібліотеку, або виконуваний файл.
EDIT 1: Перепишіть перший розділ, щоб його було легше зрозуміти. Будь ласка, прокоментуйте нижче, щоб повідомити мені, чи потрібно ще щось виправити. Дякую!
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
).
Це можна зробити з будь-яким;
#define UNICODE
#define _UNICODE
Або в налаштуваннях проекту;
Властивості проекту> Загальне> За замовчуванням проекту> Набір символів
Або в командному рядку;
/DUNICODE /D_UNICODE
Альтернатива застосовується також, якщо UNICODE не призначений для використання, переконайтеся, що значення не встановлені, та / або багатофункціональний параметр використовується в проектах і послідовно застосовується.
Не забудьте бути послідовними і між складами "Випуск" та "Налагодження".
"Очищення" збірки може видалити "мертву деревину", яка може залишитися валятися від попередніх збірок, невдалих збірок, неповних збірок та інших проблем, пов’язаних із системою побудови.
Взагалі IDE або збірка включатиме певну форму функції "чистого", але це може бути неправильно налаштовано (наприклад, в ручному makefile) або може вийти з ладу (наприклад, проміжні або результуючі бінарні файли доступні лише для читання).
Після завершення "чистого" переконайтеся, що "чистий" вдався, і всі генеровані проміжні файли (наприклад, автоматизований файл файлів) були успішно видалені.
Цей процес можна розглядати як остаточний засіб, але часто є хорошим першим кроком ; особливо, якщо нещодавно було додано код, пов'язаний з помилкою (локально або з вихідного сховища).
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
Незважаючи на те, що це досить старі запитання з декількома прийнятими відповідями, я хотів би поділитися способом вирішення неясної помилки "невизначеного посилання на".
Я використовував псевдонім для позначення 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:
Зверніть увагу на різні бібліотеки, які використовуються в 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> .
Типова поведінка 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
Різні архітектури
Можливо, ви побачите повідомлення типу:
library machine type 'x64' conflicts with target machine type 'X86'
У цьому випадку це означає, що наявні символи є для іншої архітектури, ніж та, яку ви збираєте.
У Visual Studio це пов’язано з неправильною «Платформою», і вам потрібно або вибрати відповідну, або встановити належну версію бібліотеки.
У Linux це може бути пов'язано з неправильною папкою бібліотеки (використовуючи lib
замість, lib64
наприклад).
На MacOS існує можливість доставки обох архітектур в один файл. Можливо, посилання очікує, що обидві версії будуть там, але лише одна. Також це може бути проблема з неправильною lib
/ lib64
папкою, де вибрано бібліотеку.