Co to są niezdefiniowane odniesienia / nierozwiązane błędy symboli zewnętrznych? Jakie są typowe przyczyny i jak je naprawić / zapobiec?
Możesz edytować / dodawać własne.
Co to są niezdefiniowane odniesienia / nierozwiązane błędy symboli zewnętrznych? Jakie są typowe przyczyny i jak je naprawić / zapobiec?
Możesz edytować / dodawać własne.
Odpowiedzi:
Kompilowanie programu C ++ odbywa się w kilku krokach, jak określono w 2.2 (podziękowania dla Keitha Thompsona w celach informacyjnych) :
Pierwszeństwo wśród reguł składni tłumaczenia są określone przez następujące fazy [patrz przypis] .
- Znaki fizycznego pliku źródłowego są odwzorowywane, w sposób zdefiniowany w implementacji, na podstawowy zestaw znaków źródłowych (wprowadzając znaki nowej linii dla wskaźników końca linii), jeśli to konieczne. [FANTASTYCZNA OKAZJA]
- Każde wystąpienie znaku odwrotnego ukośnika (\), po którym bezpośrednio następuje znak nowej linii, jest usuwane, łącząc fizyczne linie źródłowe, tworząc logiczne linie źródłowe. [FANTASTYCZNA OKAZJA]
- Plik źródłowy jest rozkładany na tokeny przetwarzania wstępnego (2.5) i sekwencje znaków białych znaków (w tym komentarze). [FANTASTYCZNA OKAZJA]
- Wykonywane są dyrektywy przetwarzania wstępnego, rozwijane są makropolecenia i wykonywane są wyrażenia jednoargumentowe _Pragma. [FANTASTYCZNA OKAZJA]
- Każdy źródłowy element zestawu znaków w literałach znaków lub literałach ciągów, a także każda sekwencja ucieczki i nazwa-znaków uniwersalnych w literałach znaków lub literałach łańcuchowych nieobrobionych jest konwertowany na odpowiedni element zestawu znaków wykonania; [FANTASTYCZNA OKAZJA]
- Sąsiednie ciągi literałów literowych są łączone.
- Znaki oddzielające białe znaki nie są już znaczące. Każdy token przetwarzania wstępnego jest konwertowany na token. (2.7). Otrzymane tokeny są analizowane składniowo i semantycznie oraz tłumaczone jako jednostka tłumacząca. [FANTASTYCZNA OKAZJA]
- Przetłumaczone jednostki tłumaczenia i jednostki tworzenia instancji są łączone w następujący sposób: [SNIP]
- Wszystkie odwołania do encji zewnętrznych zostały rozwiązane. Komponenty biblioteki są połączone w celu spełnienia zewnętrznych odniesień do encji, które nie zostały zdefiniowane w bieżącym tłumaczeniu. Wszystkie takie dane wyjściowe translatora są gromadzone w obrazie programu, który zawiera informacje potrzebne do wykonania w środowisku wykonawczym. (moje podkreślenie)
[przypis] Implementacje muszą zachowywać się tak, jakby miały miejsce te oddzielne fazy, chociaż w praktyce różne fazy mogą być składane razem.
Określone błędy występują podczas ostatniego etapu kompilacji, najczęściej nazywanego łączeniem. Zasadniczo oznacza to, że skompilowałeś kilka plików implementacyjnych w plikach obiektowych lub bibliotekach i teraz chcesz je ze sobą współpracować.
Powiedzmy, że zdefiniowałeś symbol a
w a.cpp
. Teraz b.cpp
zadeklarował ten symbol i użył go. Przed połączeniem po prostu zakłada, że ten symbol został gdzieś zdefiniowany , ale nie ma jeszcze znaczenia gdzie. Faza łączenia jest odpowiedzialna za znalezienie symbolu i poprawne połączenie go b.cpp
(cóż, właściwie z obiektem lub biblioteką, która go używa).
Jeśli korzystasz z Microsoft Visual Studio, zobaczysz, że projekty generują .lib
pliki. Zawierają one tabelę eksportowanych symboli i tabelę importowanych symboli. Zaimportowane symbole są rozpoznawane względem bibliotek, z którymi się łączysz, a wyeksportowane symbole są dostarczane dla bibliotek, które z nich korzystają .lib
(jeśli istnieją).
Podobne mechanizmy istnieją dla innych kompilatorów / platform.
Typowe komunikaty o błędach są error LNK2001
, error LNK1120
, error LNK2019
dla Microsoft Visual Studio i undefined reference to
symbolName dla GCC .
Kod:
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;
}
wygeneruje następujące błędy w 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
i podobne błędy w 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
Najczęstsze przyczyny to:
#pragma
(Microsoft Visual Studio)UNICODE
definicjevirtual
destruktor wymaga implementacji.Zadeklarowanie czystego destruktora nadal wymaga jego zdefiniowania (w przeciwieństwie do zwykłej funkcji):
struct X
{
virtual ~X() = 0;
};
struct Y : X
{
~Y() {}
};
int main()
{
Y y;
}
//X::~X(){} //uncomment this line for successful definition
Dzieje się tak, ponieważ niszczyciele klasy podstawowej są wywoływane, gdy obiekt jest niszczony niejawnie, dlatego wymagana jest definicja.
virtual
metody muszą być zaimplementowane lub zdefiniowane jako czyste.Jest to podobne do virtual
metod niebędących metodami bez definicji, z dodanym uzasadnieniem, że czysta deklaracja generuje fikcyjny vtable i możesz otrzymać błąd linkera bez użycia funkcji:
struct X
{
virtual void foo();
};
struct Y : X
{
void foo() {}
};
int main()
{
Y y; //linker error although there was no call to X::foo
}
Aby to zadziałało, zadeklaruj X::foo()
jako czyste:
struct X
{
virtual void foo() = 0;
};
virtual
Członkowie spoza klasyNiektórych członków należy zdefiniować, nawet jeśli nie zostaną użyte jawnie:
struct A
{
~A();
};
Poniższe spowodowałoby błąd:
A a; //destructor undefined
Implementacja może być wbudowana, w samej definicji klasy:
struct A
{
~A() {}
};
lub na zewnątrz:
A::~A() {}
Jeśli implementacja jest poza definicją klasy, ale w nagłówku, metody należy oznaczyć jako inline
zapobiegające wielokrotnej definicji.
Wszystkie używane metody składowe muszą zostać zdefiniowane, jeśli są używane.
struct A
{
void foo();
};
void foo() {}
int main()
{
A a;
a.foo();
}
Definicja powinna być
void A::foo() {}
static
elementy danych muszą być zdefiniowane poza klasą w jednej jednostce tłumaczeniowej :struct X
{
static int x;
};
int main()
{
int x = X::x;
}
//int X::x; //uncomment this line to define X::x
Inicjator może być zapewniony dla elementu static
const
danych typu integralnego lub wyliczeniowego w ramach definicji klasy; jednak użycie odr tego elementu nadal będzie wymagało definicji zakresu przestrzeni nazw, jak opisano powyżej. C ++ 11 umożliwia inicjalizację wewnątrz klasy dla wszystkich static const
członków danych.
Zwykle każda jednostka tłumaczeniowa generuje plik obiektowy, który zawiera definicje symboli zdefiniowanych w tej jednostce tłumaczeniowej. Aby użyć tych symboli, musisz połączyć się z tymi plikami obiektowymi.
W gcc należy podać wszystkie pliki obiektów, które mają zostać połączone w wierszu poleceń, lub skompilować pliki implementacji razem.
g++ -o test objectFile1.o objectFile2.o -lLibraryName
libraryName
Tutaj jest tylko gołe nazwa biblioteki, bez platformy specyficznych dodatków. Tak np. W Linuksie pliki bibliotek są zwykle wywoływane, libfoo.so
ale piszesz tylko -lfoo
. W systemie Windows ten sam plik może zostać wywołany foo.lib
, ale użyłbyś tego samego argumentu. Może być konieczne dodanie katalogu, w którym można znaleźć te pliki -L‹directory›
. Pamiętaj, aby nie pisać spacji po -l
lub -L
.
W przypadku XCode : dodaj ścieżki wyszukiwania nagłówka użytkownika -> dodaj ścieżkę wyszukiwania biblioteki -> przeciągnij i upuść aktualne odwołanie do biblioteki w folderze projektu.
W MSVS pliki dodane do projektu automatycznie łączą swoje pliki obiektowe i lib
plik zostałby wygenerowany (w powszechnym użyciu). Aby użyć symboli w osobnym projekcie, musisz uwzględnić lib
pliki w ustawieniach projektu. Odbywa się to w sekcji Linker właściwości projektu, w Input -> Additional Dependencies
. ( lib
należy dodać ścieżkę do pliku Linker -> General -> Additional Library Directories
) W przypadku korzystania z biblioteki innej firmy, która jest dostarczana z lib
plikiem, nieprzestrzeganie tego zwykle powoduje błąd.
Może się również zdarzyć, że zapomnisz dodać plik do kompilacji, w którym to przypadku plik obiektowy nie zostanie wygenerowany. W gcc dodajesz pliki do wiersza poleceń. W MSVS dodanie pliku do projektu spowoduje jego automatyczną kompilację (aczkolwiek pliki można ręcznie osobno wykluczyć z kompilacji).
W programowaniu Windows znakiem ostrzegawczym, że nie podłączyłeś niezbędnej biblioteki, jest to, że nazwa nierozwiązanego symbolu zaczyna się od __imp_
. Wyszukaj nazwę funkcji w dokumentacji i powinna ona wskazywać, której biblioteki należy użyć. Na przykład MSDN umieszcza informacje w polu u dołu każdej funkcji w sekcji o nazwie „Biblioteka”.
gcc main.c
zamiast gcc main.c other.c
(co często robią początkujący, zanim ich projekty stają się tak duże, że można zbudować pliki .o).
Typowa deklaracja zmiennej to
extern int x;
Ponieważ jest to tylko deklaracja, potrzebna jest jedna definicja . Odpowiednia definicja to:
int x;
Na przykład następujące wygenerowałoby błąd:
extern int x;
int main()
{
x = 0;
}
//int x; // uncomment this line for successful definition
Podobne uwagi dotyczą funkcji. Zadeklarowanie funkcji bez jej zdefiniowania prowadzi do błędu:
void foo(); // declaration only
int main()
{
foo();
}
//void foo() {} //uncomment this line for successful definition
Uważaj, aby zaimplementowana funkcja była dokładnie taka sama, jak zadeklarowana. Na przykład możesz mieć niedopasowane kwalifikatory 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)
Inne przykłady niedopasowań obejmują
Komunikat o błędzie z kompilatora często podaje pełną deklarację zmiennej lub funkcji, która została zadeklarowana, ale nigdy nie została zdefiniowana. Porównaj go dokładnie z podaną definicją. Upewnij się, że każdy szczegół pasuje.
#includes
nie zostały dodane do katalogu źródłowego, również należą do kategorii brakujących definicji.
Kolejność łączenia bibliotek NIE MA znaczenia, jeśli biblioteki są od siebie zależne. Ogólnie, jeśli biblioteka A
zależy od biblioteki B
, libA
MUSI pojawić się wcześniej libB
w flagach linkera.
Na przykład:
// 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;
};
Utwórz biblioteki:
$ 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
Skompilować:
$ 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
Więc powtórzyć jeszcze raz, kolejność CZY sprawa!
co to jest „niezdefiniowany odnośnik / nierozwiązany symbol zewnętrzny”
Spróbuję wyjaśnić, co to jest „niezdefiniowany odnośnik / nierozwiązany symbol zewnętrzny”.
Uwaga: używam g ++ i Linuksa i wszystkie przykłady są do tego przeznaczone
Na przykład mamy trochę kodu
// 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;
}
i
// 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);
}
Twórz pliki obiektowe
$ g++ -c src1.cpp -o src1.o
$ g++ -c src2.cpp -o src2.o
Po fazie asemblera mamy plik obiektowy, który zawiera dowolne symbole do wyeksportowania. Spójrz na symbole
$ 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]
Odrzuciłem niektóre wiersze z danych wyjściowych, ponieważ nie mają one znaczenia
Widzimy więc następujące symbole do eksportu.
[1] - this is our static (local) variable (important - Bind has a type "LOCAL")
[2] - this is our global variable
src2.cpp nic nie eksportuje i nie widzieliśmy żadnych jego symboli
Połącz nasze pliki obiektowe
$ g++ src1.o src2.o -o prog
i uruchom to
$ ./prog
123
Linker widzi wyeksportowane symbole i łączy je. Teraz próbujemy odkomentować wiersze w src2.cpp jak tutaj
// 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);
}
i odbuduj plik obiektowy
$ g++ -c src2.cpp -o src2.o
OK (bez błędów), ponieważ budujemy tylko plik obiektowy, łączenie jeszcze się nie zakończyło. Spróbuj połączyć
$ 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
Stało się tak, ponieważ nasza local_var_name jest statyczna, tzn. Nie jest widoczna dla innych modułów. Teraz głębiej. Uzyskaj wynik fazy tłumaczenia
$ 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
Widzieliśmy więc, że nie ma etykiety dla local_var_name, dlatego linker go nie znalazł. Ale jesteśmy hakerami :) i możemy to naprawić. Otwórz src1.s w edytorze tekstu i zmień
.local _ZL14local_var_name
.comm _ZL14local_var_name,4,4
do
.globl local_var_name
.data
.align 4
.type local_var_name, @object
.size local_var_name, 4
local_var_name:
.long 456789
tzn. powinieneś mieć jak poniżej
.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:
; ...
zmieniliśmy widoczność local_var_name i ustawiliśmy jego wartość na 456789. Spróbuj zbudować z niego plik obiektowy
$ g++ -c src1.s -o src2.o
ok, patrz readelf wyjście (symbole)
$ readelf --symbols src1.o
8: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 local_var_name
teraz local_var_name ma Bind GLOBAL (wcześniej LOCAL)
połączyć
$ g++ src1.o src2.o -o prog
i uruchom to
$ ./prog
123456789
ok, zhakujemy to :)
W rezultacie - „niezdefiniowany błąd odniesienia / nierozwiązany błąd symbolu zewnętrznego” ma miejsce, gdy linker nie może znaleźć symboli globalnych w plikach obiektów.
Funkcja (lub zmienna) void foo()
została zdefiniowana w programie C i próbujesz użyć jej w programie C ++:
void foo();
int main()
{
foo();
}
Linker C ++ oczekuje, że nazwy będą zniekształcone, więc musisz zadeklarować funkcję jako:
extern "C" void foo();
int main()
{
foo();
}
Odpowiednio, zamiast być zdefiniowanym w programie C, funkcja (lub zmienna) void foo()
została zdefiniowana w C ++, ale z połączeniem C:
extern "C" void foo();
i próbujesz użyć go w programie C ++ z łączeniem C ++.
Jeśli cała biblioteka jest zawarta w pliku nagłówkowym (i została skompilowana jako kod C); dołączenie będzie musiało wyglądać następująco;
extern "C" {
#include "cheader.h"
}
#ifdef __cplusplus [\n] extern"C" { [\n] #endif
i #ifdef __cplusplus [\n] } [\n] #endif
( [\n]
jest prawdziwy powrót karetki, ale nie mogę napisać to poprawnie w komentarzu).
extern "C" { #include <myCppHeader.h> }
.
Jeśli wszystko inne zawiedzie, skompiluj ponownie.
Niedawno udało mi się pozbyć nierozwiązanego błędu zewnętrznego w Visual Studio 2012, po prostu przez przekompilowanie niepoprawnego pliku. Po ponownej kompilacji błąd zniknął.
Zwykle dzieje się tak, gdy dwie (lub więcej) bibliotek mają cykliczną zależność. Biblioteka A próbuje użyć symboli w B.lib, a biblioteka B próbuje użyć symboli z A.lib. Żaden nie istnieje na początek. Podczas próby skompilowania A krok połączenia nie powiedzie się, ponieważ nie można znaleźć B.lib. A.lib zostanie wygenerowany, ale nie będzie dll. Następnie skompilujesz B, który zakończy się sukcesem i wygeneruje B.lib. Ponowna kompilacja A będzie teraz działać, ponieważ znaleziono B.lib.
MSVS wymaga określenia, które symbole mają być eksportowane i importowane za pomocą __declspec(dllexport)
i __declspec(dllimport)
.
Ta podwójna funkcjonalność jest zwykle uzyskiwana za pomocą makra:
#ifdef THIS_MODULE
#define DLLIMPEXP __declspec(dllexport)
#else
#define DLLIMPEXP __declspec(dllimport)
#endif
Makro THIS_MODULE
byłoby zdefiniowane tylko w module, który eksportuje funkcję. W ten sposób deklaracja:
DLLIMPEXP void foo();
rozwija się do
__declspec(dllexport) void foo();
i każe kompilatorowi wyeksportować funkcję, ponieważ bieżący moduł zawiera jej definicję. Uwzględniając deklarację w innym module, rozwinąłby się do
__declspec(dllimport) void foo();
i informuje kompilator, że definicja znajduje się w jednej z bibliotek, z którymi się łączysz (zobacz także 1) ).
Możesz podobnie importować / eksportować klasy:
class DLLIMPEXP X
{
};
visibility
i Windows .def
, ponieważ mają one również wpływ na nazwę i obecność symbolu.
.def
plików od wieków. Dodaj odpowiedź lub edytuj tę odpowiedź.
Jest to jeden z najbardziej mylących komunikatów o błędach, które wszyscy programiści VC ++ widzieli od czasu do czasu. Najpierw sprawmy, żeby wszystko było jasne.
A. Co to jest symbol? Krótko mówiąc, symbol to nazwa. Może to być nazwa zmiennej, nazwa funkcji, nazwa klasy, nazwa typedef lub cokolwiek innego niż te nazwy i znaki, które należą do języka C ++. Jest zdefiniowany lub wprowadzony przez bibliotekę zależności (inny zdefiniowany przez użytkownika).
B. Co jest zewnętrzne?
W VC ++ każdy plik źródłowy (.cpp, .c itp.) Jest uważany za jednostkę tłumaczeniową, kompilator kompiluje jedną jednostkę na raz i generuje jeden plik obiektowy (.obj) dla bieżącej jednostki tłumaczeniowej. (Zauważ, że każdy plik nagłówkowy, który zawiera ten plik źródłowy, zostanie wstępnie przetworzony i będzie uważany za część tej jednostki tłumaczeniowej). Wszystko w jednostce tłumaczeniowej jest uważane za wewnętrzne, wszystko inne za zewnętrzne. W C ++ możesz odwoływać się do zewnętrznego symbolu, używając słów kluczowych takich jak extern
,__declspec (dllimport)
i tak dalej.
C. Co to jest „postanowienie”? Resolve to termin łączenia. W czasie łączenia linker próbuje znaleźć definicję zewnętrzną dla każdego symbolu w plikach obiektowych, który nie może znaleźć swojej definicji wewnętrznie. Zakres tego procesu wyszukiwania obejmuje:
Ten proces wyszukiwania nazywa się rozwiązać.
D. Wreszcie, dlaczego nierozwiązany symbol zewnętrzny? Jeśli linker nie może znaleźć definicji zewnętrznej dla symbolu, który nie ma definicji wewnętrznie, zgłasza błąd nierozwiązanego symbolu zewnętrznego.
E. Możliwe przyczyny LNK2019 : Nierozwiązany błąd symbolu zewnętrznego. Wiemy już, że ten błąd wynika z faktu, że linker nie znalazł definicji symboli zewnętrznych, możliwe przyczyny można posortować jako:
Na przykład, jeśli mamy funkcję o nazwie foo zdefiniowaną w pliku a.cpp:
int foo()
{
return 0;
}
W b.cpp chcemy wywołać funkcję foo, więc dodajemy
void foo();
aby zadeklarować funkcję foo () i wywołać ją w innym ciele funkcji, powiedz bar()
:
void bar()
{
foo();
}
Teraz, kiedy skompilujesz ten kod, pojawi się błąd LNK2019 narzekający, że foo jest nierozwiązanym symbolem. W tym przypadku wiemy, że foo () ma swoją definicję w a.cpp, ale różni się od tej, którą wywołujemy (inna wartość zwracana). Tak jest w przypadku definicji.
Jeśli chcemy wywołać niektóre funkcje w bibliotece, ale biblioteka importu nie jest dodawana do dodatkowej listy zależności (ustawionej od Project | Properties | Configuration Properties | Linker | Input | Additional Dependency
:) w ustawieniach projektu. Teraz linker zgłosi LNK2019, ponieważ definicja nie istnieje w bieżącym zakresie wyszukiwania.
Nieokreślone szablony muszą mieć swoje definicje widoczne dla wszystkich jednostek tłumaczeniowych, które ich używają. Oznacza to, że nie można oddzielić definicji szablonu do pliku implementacji. Jeśli musisz oddzielić implementację, zwykłym obejściem jest posiadanie impl
pliku, który dołączasz na końcu nagłówka, który deklaruje szablon. Częstą sytuacją jest:
template<class T>
struct X
{
void foo();
};
int main()
{
X<int> x;
x.foo();
}
//differentImplementationFile.cpp
template<class T>
void X<T>::foo()
{
}
Aby to naprawić, musisz przenieść definicję X::foo
do pliku nagłówka lub innego miejsca widocznego dla używającej go jednostki tłumaczeniowej.
Specjalistyczne szablony można zaimplementować w pliku implementacji i implementacja nie musi być widoczna, ale specjalizacja musi zostać wcześniej zadeklarowana.
W celu uzyskania dalszych wyjaśnień i innego możliwego rozwiązania (wyraźna instancja) zobacz to pytanie i odpowiedź .
niezdefiniowane odniesienie do WinMain@16
lub podobne „nietypowe” main()
odniesienie do punktu wejścia (szczególnie dlastudio wizualne).
Możliwe, że nie wybrałeś odpowiedniego typu projektu z faktycznym IDE. IDE może chcieć powiązać np. Projekty aplikacji Windows z taką funkcją punktu wejścia (jak określono w brakującym odnośniku powyżej), zamiast powszechnie używanego int main(int argc, char** argv);
podpisu.
Jeśli twoje IDE obsługuje projekty Plain Console, możesz wybrać ten typ projektu zamiast projektu aplikacji Windows.
Oto Przypadek 1 i Przypadek 2 obsługiwane bardziej szczegółowo od świata rzeczywistego problemu.
WinMain
. Prawidłowe programy C ++ wymagają main
.
Pakiet Visual Studio NuGet wymaga aktualizacji do nowej wersji zestawu narzędzi
Właśnie miałem ten problem, próbując połączyć libpng z Visual Studio 2013. Problem polega na tym, że plik pakietu miał tylko biblioteki dla Visual Studio 2010 i 2012.
Prawidłowym rozwiązaniem jest nadzieja, że programista wyda zaktualizowany pakiet, a następnie uaktualni, ale zadziałało dla mnie, włamując się do dodatkowego ustawienia dla VS2013, wskazując na pliki biblioteki VS2012.
Zredagowałem pakiet (w packages
folderze w katalogu rozwiązania), znajdując packagename\build\native\packagename.targets
i wewnątrz tego pliku, kopiując wszystkie v110
sekcje. Zmieniłem v110
się v120
w polach stan tylko będąc bardzo ostrożny, aby zostawić wszystko jak ścieżki nazw plików v110
. To po prostu pozwoliło Visual Studio 2013 połączyć się z bibliotekami na 2012 rok, w tym przypadku zadziałało.
Załóżmy, że masz duży projekt napisany w języku C ++, który zawiera tysiące plików .cpp i tysiąc plików .h. Powiedzmy, że projekt zależy również od dziesięciu bibliotek statycznych. Powiedzmy, że jesteśmy w systemie Windows i tworzymy nasz projekt w Visual Studio 20xx. Gdy naciśniesz Ctrl + F7 Visual Studio, aby rozpocząć kompilowanie całego rozwiązania (załóżmy, że mamy tylko jeden projekt w rozwiązaniu)
Jakie jest znaczenie kompilacji?
Drugi krok kompilacji wykonuje Linker. Linker powinien scalić cały plik obiektowy i w końcu zbudować wynik (który może być plikiem wykonywalnym lub biblioteką)
Kroki w łączeniu projektu
error LNK2001: unresolved external symbol "void __cdecl foo(void)" (?foo@@YAXXZ)
Obserwacja
Jak rozwiązać ten rodzaj błędu
Błąd czasu kompilatora:
Błąd czasu łącznika
#pragma once
do zezwalania kompilatorowi na niewłączanie jednego nagłówka, jeśli był już zawarty w bieżącym .cpp, które są kompilowaneNiedawno miałem ten problem i okazało się, że był to błąd w Visual Studio Express 2013 . Musiałem usunąć plik źródłowy z projektu i ponownie go dodać, aby usunąć błąd.
Kroki do wypróbowania, jeśli uważasz, że może to być błąd w kompilatorze / IDE:
Większość współczesnych linkerów zawiera pełną opcję, która drukuje w różnym stopniu;
Dla gcc i clang; zazwyczaj dodajesz -v -Wl,--verbose
lub -v -Wl,-v
do linii poleceń. Więcej informacji można znaleźć tutaj;
W przypadku MSVC /VERBOSE
(w szczególności /VERBOSE:LIB
) jest dodawany do wiersza poleceń łącza.
/VERBOSE
opcji linkera .Połączony plik .lib jest powiązany z plikiem .dll
Miałem ten sam problem. Powiedz, że mam projekty MyProject i TestProject. Skutecznie połączyłem plik lib MyProject z TestProject. Ten plik lib został jednak utworzony, ponieważ zbudowano bibliotekę DLL dla MyProject. Ponadto nie zawierałem kodu źródłowego dla wszystkich metod w MyProject, a jedynie dostęp do punktów wejścia biblioteki DLL.
Aby rozwiązać problem, zbudowałem MyProject jako LIB i połączyłem TestProject z tym plikiem .lib (kopiuję wklejony wygenerowany plik .lib do folderu TestProject). Następnie mogę ponownie zbudować MyProject jako DLL. Kompiluje się, ponieważ biblioteka, z którą jest połączony TestProject, zawiera kod dla wszystkich metod w klasach w MyProject.
Ponieważ ludzie wydają się być kierowani do tego pytania, jeśli chodzi o błędy linkera, dodam to tutaj.
Jednym z możliwych powodów błędów linkera w GCC 5.2.0 jest to, że nowa ABI biblioteki libstdc ++ jest teraz domyślnie wybierana.
Jeśli pojawią się błędy linkera dotyczące niezdefiniowanych odwołań do symboli, które dotyczą typów w przestrzeni nazw std :: __ cxx11 lub znaczniku [abi: cxx11], oznacza to prawdopodobnie, że próbujesz połączyć ze sobą pliki obiektów, które zostały skompilowane z różnymi wartościami dla _GLIBCXX_USE_CXX11_ABI makro. Zdarza się to często podczas łączenia się z biblioteką innej firmy, która została skompilowana ze starszą wersją GCC. Jeśli biblioteki innej firmy nie można odbudować przy użyciu nowego ABI, należy ponownie skompilować kod ze starym ABI.
Więc jeśli nagle pojawią się błędy linkera podczas przejścia na GCC po 5.1.0, warto to sprawdzić.
Opakowanie wokół GNU ld, które nie obsługuje skryptów konsolidatora
Niektóre pliki .so są w rzeczywistości skryptami konsolidatora GNU ld , np. Plik libtbb.so to plik tekstowy ASCII o następującej treści:
INPUT (libtbb.so.2)
Niektóre bardziej złożone kompilacje mogą tego nie obsługiwać. Na przykład, jeśli dodasz -v do opcji kompilatora, zobaczysz, że mwdip otoki mainwin gcc odrzuca pliki poleceń skryptu linkera na pełnej liście wyjściowej bibliotek, do których ma się połączyć. Prostym obejściem jest zastąpienie polecenia wejściowego skryptu linkera plik z kopią pliku zamiast (lub dowiązaniem symbolicznym), np
cp libtbb.so.2 libtbb.so
Lub możesz zastąpić argument -l pełną ścieżką .so, np. Zamiast -ltbb
do/home/foo/tbb-4.3/linux/lib/intel64/gcc4.4/libtbb.so.2
libfoo
zależy od libbar
tego, twój link poprawnie wstawia libfoo
wcześniej libbar
.undefined reference to
czymś błędami.#include
D i faktycznie są zdefiniowane w bibliotekach, które są linkami.Przykłady znajdują się w C. Równie dobrze mogłyby to być 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;
}
Budujesz swoją bibliotekę statyczną:
$ gcc -c -o my_lib.o my_lib.c
$ ar rcs libmy_lib.a my_lib.o
Kompilujesz swój program:
$ gcc -I. -c -o eg1.o eg1.c
Próbujesz połączyć go z libmy_lib.a
i nie powiedzie się:
$ 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
Ten sam wynik, jeśli kompilujesz i łączysz w jednym kroku, na przykład:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
/tmp/ccQk1tvs.o: In function `main':
eg1.c:(.text+0x5): undefined reference to `hw'
collect2: error: ld returned 1 exit status
libz
eg2.c
#include <zlib.h>
#include <stdio.h>
int main()
{
printf("%s\n",zlibVersion());
return 0;
}
Skompiluj swój program:
$ gcc -c -o eg2.o eg2.c
Spróbuj połączyć swój program libz
i zawieść:
$ 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
To samo, jeśli kompilujesz i łączysz za jednym razem:
$ 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
I wariant na przykładzie 2 obejmujący pkg-config
:
$ gcc -o eg2 $(pkg-config --libs zlib) eg2.o
eg2.o: In function `main':
eg2.c:(.text+0x5): undefined reference to `zlibVersion'
W sekwencji plików obiektowych i bibliotek, które chcesz połączyć w celu utworzenia programu, umieszczasz biblioteki przed plikami obiektowymi, które się do nich odnoszą. Musisz umieścić biblioteki po plikach obiektowych, które się do nich odnoszą.
Podłącz przykład 1 poprawnie:
$ gcc -o eg1 eg1.o -L. -lmy_lib
Sukces:
$ ./eg1
Hello World
Podłącz przykład 2 poprawnie:
$ gcc -o eg2 eg2.o -lz
Sukces:
$ ./eg2
1.2.8
Połącz pkg-config
poprawnie przykładową odmianę 2 :
$ gcc -o eg2 eg2.o $(pkg-config --libs zlib)
$ ./eg2
1.2.8
Odczytywanie jest odtąd opcjonalne .
Domyślnie polecenie powiązania wygenerowane przez GCC w twojej dystrybucji zużywa pliki w powiązaniu od lewej do prawej w sekwencji wiersza poleceń. Gdy stwierdzi, że plik odnosi się do czegoś i nie zawiera definicji tego, szuka definicji w plikach po prawej stronie. Jeśli w końcu znajdzie definicję, odwołanie zostanie rozwiązane. Jeśli na końcu jakieś odniesienia pozostaną nierozwiązane, połączenie się nie powiedzie: linker nie szuka wstecz.
Najpierw przykład 1 ze statyczną bibliotekąmy_lib.a
Biblioteka statyczna to zindeksowane archiwum plików obiektowych. Gdy linker znajdzie -lmy_lib
sekwencję łączenia i ./libmy_lib.a
zorientuje się, że odnosi się to do biblioteki statycznej , chce wiedzieć, czy twój program potrzebuje plików obiektowych libmy_lib.a
.
Jest tylko plik obiektowy libmy_lib.a
, a mianowicie my_lib.o
, i jest zdefiniowana tylko jedna rzecz my_lib.o
, a mianowicie funkcja hw
.
Linker zdecyduje, że twój program potrzebuje my_lib.o
wtedy i tylko wtedy, gdy już wie, że twój program się odwołuje hw
, w co najmniej jednym pliku obiektowym, który już dodał do programu, i że żaden z plików obiektowych, który już dodał, nie zawiera definicja dla hw
.
Jeśli to prawda, linker wyodrębni kopię my_lib.o
z biblioteki i doda ją do twojego programu. Następnie twój program zawiera definicję hw
, więc jego odniesienia do hw
zostały rozwiązane .
Podczas próby połączenia programu, takiego jak:
$ gcc -o eg1 -L. -lmy_lib eg1.o
linker nie dodał eg1.o
do programu, gdy widzi
-lmy_lib
. Ponieważ w tym momencie nie widziałem eg1.o
. Twój program nie ma jeszcze dokonywać żadnych odniesień do hw
: to jeszcze nie dokonywać żadnych odniesień w ogóle , ponieważ wszystkie odniesienia to sprawia, że są eg1.o
.
Linker nie dodaje my_lib.o
się do programu i nie ma dalszego zastosowania libmy_lib.a
.
Następnie znajduje eg1.o
i dodaje do programu. Plik obiektowy w sekwencji łączenia jest zawsze dodawany do programu. Teraz program zawiera odniesienie hw
i nie zawiera definicji hw
; ale w sekwencji łączenia nie pozostało nic, co mogłoby zapewnić brakującą definicję. Odwołanie do hw
kończy się nierozwiązane , a powiązanie kończy się niepowodzeniem.
Po drugie, przykład 2 , z biblioteką współdzielonąlibz
Biblioteka współdzielona nie jest archiwum plików obiektowych ani nic podobnego. Bardziej przypomina program , który nie ma main
funkcji i zamiast tego wyświetla wiele innych symboli, które definiuje, aby inne programy mogły z nich korzystać w czasie wykonywania.
Dzisiaj wielu dystrybucjach Linuksa skonfigurować swój GCC toolchain tak, że jego sterowniki językowe ( gcc
, g++
, gfortran
etc) poinstruować łącznikiem systemowym ( ld
), aby połączyć współdzielonych bibliotek w miarę potrzeb podstawie. Masz jedną z tych dystrybucji.
Oznacza to, że gdy linker znajdzie -lz
sekwencję łączenia i /usr/lib/x86_64-linux-gnu/libz.so
zorientuje się, że odnosi się to do biblioteki współdzielonej (powiedzmy) , chce wiedzieć, czy jakiekolwiek odwołania dodane do programu, które nie zostały jeszcze zdefiniowane, mają definicje, które są eksportowane przezlibz
Jeśli to prawda, linker nie skopiuje żadnych fragmentów libz
i nie doda ich do twojego programu; zamiast tego po prostu dokona kodu twojego programu, aby:
W czasie wykonywania program ładujący program ładuje kopię libz
do tego samego procesu, co program, za każdym razem, gdy ładuje kopię programu, aby go uruchomić.
W czasie wykonywania, ilekroć twój program odwołuje się do czegoś, co jest zdefiniowane w
libz
, to odwołanie korzysta z definicji wyeksportowanej przez kopię libz
w tym samym procesie.
Twój program chce odwoływać się tylko do jednej rzeczy, która ma wyeksportowaną definicję libz
, a mianowicie funkcji zlibVersion
, do której odwołuje się tylko raz eg2.c
. Jeśli linker doda to odwołanie do programu, a następnie znajdzie wyeksportowaną definicję libz
, odwołanie zostanie rozwiązane
Ale kiedy próbujesz połączyć program w następujący sposób:
gcc -o eg2 -lz eg2.o
kolejność zdarzeń jest niepoprawna w taki sam sposób jak w przykładzie 1. W momencie, gdy linker znajdzie -lz
, nie ma żadnych odniesień do niczego w programie: wszystkie są w eg2.o
, czego jeszcze nie widziano. Linker decyduje więc, że nie ma sensu libz
. Kiedy osiągnie eg2.o
, dodaje go do programu, a następnie ma niezdefiniowane odniesienie zlibVersion
, sekwencja łączenia jest zakończona; to odniesienie jest nierozwiązane, a połączenie nie działa.
Wreszcie pkg-config
odmiana z przykładu 2 ma teraz oczywiste wytłumaczenie. Po rozszerzeniu powłoki:
gcc -o eg2 $(pkg-config --libs zlib) eg2.o
staje się:
gcc -o eg2 -lz eg2.o
co jest znowu tylko przykładem 2.
Połączenie:
gcc -o eg2 -lz eg2.o
działa dobrze dla Ciebie!
(Lub: To połączenie działało dobrze dla ciebie, powiedzmy, Fedora 23, ale nie działa w Ubuntu 16.04)
Wynika to z faktu, że dystrybucja, w której działa łączenie, jest jedną z tych, które nie konfigurują łańcucha narzędzi GCC do łączenia bibliotek współdzielonych w razie potrzeby .
W tamtych czasach normalne dla systemów uniksowych było łączenie bibliotek statycznych i współdzielonych według różnych reguł. Biblioteki statyczne w sekwencji łączenia zostały połączone zgodnie z potrzebami wyjaśnionymi w przykładzie 1, ale biblioteki wspólne zostały połączone bezwarunkowo.
Takie zachowanie jest ekonomiczne w czasie połączenia, ponieważ linker nie musi zastanawiać się, czy program potrzebuje biblioteki współdzielonej: jeśli jest to biblioteka współdzielona, połącz ją. A większość bibliotek w większości powiązań to biblioteki współdzielone. Ale są też wady: -
Jest to nieopłacalne w czasie wykonywania , ponieważ może powodować ładowanie bibliotek współdzielonych wraz z programem, nawet jeśli ich nie potrzebuje.
Różne reguły łączenia dla bibliotek statycznych i współdzielonych mogą być mylące dla niedoświadczonych programistów, którzy mogą nie wiedzieć, czy -lfoo
w ich powiązaniu rozwiążą się /some/where/libfoo.a
lub nie /some/where/libfoo.so
, i mogą nie rozumieć różnicy między bibliotekami współdzielonymi a statycznymi.
Ten kompromis doprowadził dziś do schizmatyckiej sytuacji. Niektóre dystrybucje zmieniły swoje reguły łączenia GCC dla bibliotek współużytkowanych, tak aby zasada według potrzeb miała zastosowanie do wszystkich bibliotek. Niektóre dystrybucje utknęły w starym stylu.
Jeśli tylko to zrobię:
$ gcc -o eg1 -I. -L. -lmy_lib eg1.c
z pewnością gcc eg1.c
najpierw musi się skompilować , a następnie połączyć wynikowy plik obiektowy libmy_lib.a
. Jak więc nie wiedzieć, że plik obiektowy jest potrzebny podczas łączenia?
Ponieważ kompilacja i łączenie za pomocą jednego polecenia nie zmienia kolejności sekwencji łączenia.
Po uruchomieniu powyższego polecenia gcc
okazuje się, że chcesz kompilację + powiązanie. Więc za kulisami generuje polecenie kompilacji i uruchamia je, a następnie generuje polecenie łączenia i uruchamia je tak, jakbyś uruchomił dwa polecenia:
$ gcc -I. -c -o eg1.o eg1.c
$ gcc -o eg1 -L. -lmy_lib eg1.o
Tak więc połączenie nie tak jak to robi, jeśli nie uruchomić te dwie komendy. Jedyną różnicą, którą zauważasz w przypadku niepowodzenia, jest to, że gcc wygenerował tymczasowy plik obiektowy w przypadku kompilacji + łącza, ponieważ nie mówisz, aby go używał eg1.o
. Widzimy:
/tmp/ccQk1tvs.o: In function `main'
zamiast:
eg1.o: In function `main':
Kolejność określania wzajemnie zależnych bibliotek połączonych jest nieprawidłowa
Umieszczenie współzależnych bibliotek w niewłaściwej kolejności to tylko jeden ze sposobów, w jaki można uzyskać pliki wymagające definicji rzeczy, które pojawią się później w powiązaniu, niż pliki, które zawierają definicje. Umieszczenie bibliotek przed odnoszącymi się do nich plikami obiektowymi to kolejny sposób na popełnienie tego samego błędu.
Biorąc pod uwagę fragment kodu typu szablonu z operatorem znajomego (lub funkcją);
template <typename T>
class Foo {
friend std::ostream& operator<< (std::ostream& os, const Foo<T>& a);
};
operator<<
Jest zadeklarowana jako funkcja bez szablonu. Dla każdego T
używanego typu Foo
musi istnieć szablon bez szablonu operator<<
. Na przykład, jeśli istnieje Foo<int>
zadeklarowany typ , musi istnieć implementacja operatora w następujący sposób;
std::ostream& operator<< (std::ostream& os, const Foo<int>& a) {/*...*/}
Ponieważ nie jest zaimplementowany, linker nie może go znaleźć i powoduje błąd.
Aby to poprawić, możesz zadeklarować operator szablonu przed Foo
typem, a następnie zadeklarować jako przyjaciela odpowiednią instancję. Składnia jest trochę niezręczna, ale wygląda następująco;
// 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
}
Powyższy kod ogranicza przyjaźń operatora do odpowiedniej instancji Foo
, tj. operator<< <int>
Instancja jest ograniczona do dostępu do prywatnych członków instancji Foo<int>
.
Alternatywy obejmują;
Umożliwiając przyjaźni objęcie wszystkich instancji szablonów w następujący sposób;
template <typename T>
class Foo {
template <typename T1>
friend std::ostream& operator<<(std::ostream& os, const Foo<T1>& a);
// ...
};
Lub implementacja dla operator<<
może być wykonana inline wewnątrz definicji klasy;
template <typename T>
class Foo {
friend std::ostream& operator<<(std::ostream& os, const Foo& a)
{ /*...*/ }
// ...
};
Zauważ , że gdy deklaracja operatora (lub funkcji) pojawia się tylko w klasie, nazwa nie jest dostępna dla „normalnego” wyszukiwania, tylko dla wyszukiwania zależnego od argumentu, z cppreference ;
Nazwa po raz pierwszy zadeklarowana w deklaracji znajomego w klasie lub szablonie klasy X staje się członkiem najbardziej wewnętrznej otaczającej przestrzeni nazw X, ale nie jest dostępna dla wyszukiwania (z wyjątkiem wyszukiwania zależnego od argumentu, który uwzględnia X), chyba że pasująca deklaracja w zakresie przestrzeni nazw to opatrzony...
Więcej informacji na temat znajomych z szablonów można znaleźć na cppreference i C ++ FAQ .
Lista kodów pokazująca powyższe techniki .
Jako notatka dodatkowa do nieudanego przykładu kodu; g ++ ostrzega o tym w następujący sposób
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)
Błędy linkera mogą wystąpić, gdy plik nagłówka i powiązana z nim biblioteka współdzielona (plik .lib) nie są zsynchronizowane. Pozwól mi wyjaśnić.
Jak działają linkery? Linker dopasowuje deklarację funkcji (zadeklarowaną w nagłówku) z jej definicją (w bibliotece współdzielonej) poprzez porównanie ich podpisów. Możesz otrzymać błąd linkera, jeśli linker nie znajdzie idealnie pasującej definicji funkcji.
Czy nadal można uzyskać błąd linkera, mimo że deklaracja i definicja wydają się pasować? Tak! Mogą wyglądać tak samo w kodzie źródłowym, ale tak naprawdę zależy to od tego, co widzi kompilator. Zasadniczo możesz skończyć z taką sytuacją:
// header1.h
typedef int Number;
void foo(Number);
// header2.h
typedef float Number;
void foo(Number); // this only looks the same lexically
Zwróć uwagę, że chociaż obie deklaracje funkcji wyglądają identycznie w kodzie źródłowym, ale tak naprawdę różnią się w zależności od kompilatora.
Możesz zapytać, jak kończy się taka sytuacja? Uwzględnij oczywiście ścieżki ! Jeśli podczas kompilowania biblioteki współużytkowanej ścieżka włączania prowadzi do header1.h
i używasz header2.h
w swoim własnym programie, pozostaniesz drapiąc się po nagłówku, zastanawiając się, co się stało (zamierzona gra słów).
Przykład tego, jak może się to zdarzyć w prawdziwym świecie, wyjaśniono poniżej.
Mam dwa projekty: graphics.lib
i main.exe
. Oba projekty zależą od common_math.h
. Załóżmy, że biblioteka eksportuje następującą funkcję:
// graphics.lib
#include "common_math.h"
void draw(vec3 p) { ... } // vec3 comes from common_math.h
A potem dołącz do biblioteki w swoim własnym projekcie.
// main.exe
#include "other/common_math.h"
#include "graphics.h"
int main() {
draw(...);
}
Bum! Wystąpił błąd linkera i nie masz pojęcia, dlaczego się nie udaje. Powodem jest to, że wspólna biblioteka używa różnych wersji tego samego dołączenia common_math.h
(w tym przykładzie pokazałem, że zawiera inną ścieżkę, ale nie zawsze może być to takie oczywiste. Może ścieżka dołączania jest inna w ustawieniach kompilatora) .
Zauważ, że w tym przykładzie linker powiedziałby ci, że nie mógł znaleźć draw()
, kiedy w rzeczywistości wiesz, że jest on eksportowany przez bibliotekę. Mógłbyś godzinami drapać się w głowie, zastanawiając się, co poszło nie tak. Chodzi o to, że linker widzi inną sygnaturę, ponieważ typy parametrów są nieco inne. W tym przykładzie vec3
jest inny typ w obu projektach, jeśli chodzi o kompilator. Może się tak zdarzyć, ponieważ pochodzą z dwóch nieco różnych plików dołączanych (być może pliki dołączone pochodzą z dwóch różnych wersji biblioteki).
DUMPBIN jest twoim przyjacielem, jeśli korzystasz z Visual Studio. Jestem pewien, że inne kompilatory mają inne podobne narzędzia.
Proces przebiega następująco:
[1] Przez projekt rozumiem zestaw plików źródłowych, które są ze sobą połączone w celu utworzenia biblioteki lub pliku wykonywalnego.
EDYCJA 1: Przepisano pierwszą część, aby była łatwiejsza do zrozumienia. Proszę o komentarz poniżej, aby poinformować mnie, czy coś innego wymaga naprawy. Dzięki!
UNICODE
definicjeKompilacja Windows UNICODE jest budowana z TCHAR
itp. Zdefiniowanym jako wchar_t
itd. Gdy nie buduje się z UNICODE
definicją zdefiniowaną jako kompilacja TCHAR
zdefiniowana jako char
itd. Te UNICODE
i _UNICODE
definicje wpływają na wszystkie typy ciągów „ T
” ; LPTSTR
, LPCTSTR
I ich Ełku.
Zbudowanie jednej biblioteki ze UNICODE
zdefiniowaną i próba połączenia jej w projekcie, który UNICODE
nie jest zdefiniowany, spowoduje błędy linkera, ponieważ wystąpi niedopasowanie definicji TCHAR
; char
vs wchar_t
.
Błąd zwykle obejmuje funkcję o wartości typu pochodnego char
lub wchar_t
, mogą obejmować również std::basic_string<>
itp. Podczas przeglądania funkcji dotkniętego w kodzie, to często będzie odniesienie do TCHAR
lub std::basic_string<TCHAR>
itd. Jest to znak ostrzegawcze, że kod został pierwotnie przeznaczony zarówno dla Unicode i Multi-Byte Character (lub „taśmy”) build .
Aby to naprawić, zbuduj wszystkie wymagane biblioteki i projekty ze spójną definicją UNICODE
(i _UNICODE
).
Można to zrobić za pomocą albo;
#define UNICODE
#define _UNICODE
Lub w ustawieniach projektu;
Właściwości projektu> Ogólne> Domyślne ustawienia projektu> Zestaw znaków
Lub w wierszu poleceń;
/DUNICODE /D_UNICODE
Alternatywa ma również zastosowanie, jeśli UNICODE nie jest przeznaczone do użycia, upewnij się, że definicje nie są ustawione i / lub ustawienie wielu znaków jest stosowane w projektach i konsekwentnie stosowane.
Nie zapomnij również zachować spójności między kompilacjami „Release” i „Debug”.
„Czysta” kompilacja może usunąć „martwe drewno”, które można pozostawić leżące wokół poprzednich kompilacji, kompilacji zakończonych niepowodzeniem, niekompletnych kompilacji i innych problemów kompilacji związanych z systemem kompilacji.
Zasadniczo IDE lub kompilacja będzie zawierać pewną formę funkcji „czystego”, ale może nie być poprawnie skonfigurowana (np. W ręcznym pliku makefile) lub może zawieść (np. Pośrednie lub wynikowe pliki binarne są tylko do odczytu).
Po zakończeniu „czystego” sprawdź, czy „czyste” powiodło się i czy wszystkie wygenerowane pliki pośrednie (np. Automatyczny plik makefile) zostały pomyślnie usunięte.
Proces ten można postrzegać jako ostateczność, ale często stanowi on dobry pierwszy krok ; zwłaszcza jeśli niedawno dodano kod związany z błędem (lokalnie lub ze źródłowego repozytorium).
const
deklaracjach / definicjach zmiennych (tylko C ++)Dla osób pochodzących z C może być zaskoczeniem, że w C ++ const
zmienne globalne mają wewnętrzne (lub statyczne) powiązanie. W przypadku C tak nie było, ponieważ wszystkie zmienne globalne są domyślnie extern
(tzn. Gdy static
brakuje słowa kluczowego).
Przykład:
// 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
}
poprawne byłoby użycie pliku nagłówka i dołączenie go do pliku2.cpp i pliku1.cpp
extern const int test;
extern int test2;
Alternatywnie można zadeklarować const
zmienną w pliku1.cpp jawnieextern
Mimo że jest to dość stare pytanie z wieloma zaakceptowanymi odpowiedziami, chciałbym podzielić się z Tobą, jak rozwiązać niejasny „niezdefiniowany odnośnik do” błędu.
Używałem aliasu, aby odnieść się do std::filesystem::path
: system plików znajduje się w standardowej bibliotece od C ++ 17, ale mój program musiał również skompilować w C ++ 14, więc zdecydowałem się użyć zmiennego aliasu:
#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
Załóżmy, że mam trzy pliki: main.cpp, file.h, file.cpp:
Zwróć uwagę na różne biblioteki używane w main.cpp i file.h. Ponieważ main.cpp # zawiera „ file.h ” po < systemie plików >, używana wersja systemu plików to C ++ 17 . Kiedyś kompilowałem program za pomocą następujących poleceń:
$ g++ -g -std=c++17 -c main.cpp
-> kompiluje main.cpp do main.o
$ g++ -g -std=c++17 -c file.cpp
-> kompiluje file.cpp i file.h do file.o
$ g++ -g -std=c++17 -o executable main.o file.o -lstdc++fs
-> łączy main.o i file.o
W ten sposób każda funkcja zawarta w pliku.o i używana w main.o, która wymagała,path_t
dawała błędy „niezdefiniowanego odwołania”, ponieważ main.o odnosiło się do pliku.o,std::filesystem::path
ale do . std::experimental::filesystem::path
Aby to naprawić, po prostu musiałem zmienić <eksperymentalny :: system plików> w pliku.h na <system plików> .
Domyślne zachowanie gcc polega na tym, że wszystkie symbole są widoczne. Jednak gdy jednostki tłumaczeniowe są budowane z opcją -fvisibility=hidden
, tylko funkcje / symbole oznaczone symbolem __attribute__ ((visibility ("default")))
są zewnętrzne w wynikowym obiekcie współdzielonym.
Możesz sprawdzić, czy symbole, których szukasz, są zewnętrzne, wywołując:
# -D shows (global) dynamic symbols that can be used from the outside of XXX.so
nm -D XXX.so | grep MY_SYMBOL
ukryte / lokalne symbole są oznaczone nm
małymi literami, na przykład t
zamiast `T dla sekcji kodu:
nm XXX.so
00000000000005a7 t HIDDEN_SYMBOL
00000000000005f8 T VISIBLE_SYMBOL
Możesz także użyć nm
tej opcji -C
do rozplątania nazw (jeśli użyto C ++).
Podobnie jak w bibliotekach dll systemu Windows, funkcje publiczne można oznaczać za pomocą definicji, na przykład DLL_PUBLIC
zdefiniowanej jako:
#define DLL_PUBLIC __attribute__ ((visibility ("default")))
DLL_PUBLIC int my_public_function(){
...
}
Co w przybliżeniu odpowiada wersji Windows / MSVC:
#ifdef BUILDING_DLL
#define DLL_PUBLIC __declspec(dllexport)
#else
#define DLL_PUBLIC __declspec(dllimport)
#endif
Więcej informacji o widoczności można znaleźć na wiki gcc.
Kiedy jednostka tłumacząca jest kompilowana z -fvisibility=hidden
wynikowymi symbolami, nadal ma zewnętrzne powiązanie (pokazane za pomocą symbolu wielkimi literami nm
) i może być używane do zewnętrznego łączenia bez problemu, jeśli pliki obiektowe staną się częścią bibliotek statycznych. Łączenie staje się lokalne tylko wtedy, gdy pliki obiektowe są połączone z biblioteką współdzieloną.
Aby sprawdzić, które symbole w pliku obiektowym są ukryte, uruchom:
>>> objdump -t XXXX.o | grep hidden
0000000000000000 g F .text 000000000000000b .hidden HIDDEN_SYMBOL1
000000000000000b g F .text 000000000000000b .hidden HIDDEN_SYMBOL2
nm -CD
lub nm -gCD
do przeglądania zewnętrznych symboli. Zobacz także Widoczność na wiki GCC.
Różne architektury
Może zostać wyświetlony komunikat:
library machine type 'x64' conflicts with target machine type 'X86'
W takim przypadku oznacza to, że dostępne symbole dotyczą innej architektury niż ta, dla której się kompilujesz.
W Visual Studio dzieje się tak z powodu niewłaściwej „Platformy” i musisz albo wybrać właściwą, albo zainstalować odpowiednią wersję biblioteki.
W systemie Linux może to wynikać z niewłaściwego folderu biblioteki (przy użyciu lib
lib64
na przykład zamiast zamiast ).
W systemie MacOS istnieje możliwość wysyłki obu architektur do tego samego pliku. Może być tak, że link spodziewa się, że będą tam obie wersje, ale jest tylko jedna. Może to być również problem z niewłaściwym folderem lib
/, w lib64
którym biblioteka jest pobierana.