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.
c++
linker-errors
undefined-reference
c++-faq
unresolved-external
Luchian Grigore
źródło
źródło
Odpowiedzi:
Kompilowanie programu C ++ odbywa się w kilku krokach, jak określono w 2.2 (podziękowania dla Keitha Thompsona w celach informacyjnych) :
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
wa.cpp
. Terazb.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 gob.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 iundefined reference to
symbolName dla GCC .Kod:
wygeneruje następujące błędy w GCC :
i podobne błędy w Microsoft Visual Studio :
Najczęstsze przyczyny to:
#pragma
(Microsoft Visual Studio)UNICODE
definicjeźródło
Członkowie klasy:
Czysty
virtual
destruktor wymaga implementacji.Zadeklarowanie czystego destruktora nadal wymaga jego zdefiniowania (w przeciwieństwie do zwykłej funkcji):
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:Aby to zadziałało, zadeklaruj
X::foo()
jako czyste:virtual
Członkowie spoza klasyNiektórych członków należy zdefiniować, nawet jeśli nie zostaną użyte jawnie:
Poniższe spowodowałoby błąd:
Implementacja może być wbudowana, w samej definicji klasy:
lub na zewnątrz:
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.
Częstym błędem jest zapominanie o określeniu nazwy:
Definicja powinna być
static
elementy danych muszą być zdefiniowane poza klasą w jednej jednostce tłumaczeniowej :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 wszystkichstatic const
członków danych.źródło
Brak połączenia z odpowiednimi bibliotekami / plikami obiektowymi lub kompilacją plików implementacyjnych
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.
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łanyfoo.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, wInput -> Additional Dependencies
. (lib
należy dodać ścieżkę do plikuLinker -> General -> Additional Library Directories
) W przypadku korzystania z biblioteki innej firmy, która jest dostarczana zlib
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”.źródło
gcc main.c
zamiastgcc 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).Zadeklarowano, ale nie zdefiniowano zmiennej lub funkcji.
Typowa deklaracja zmiennej to
Ponieważ jest to tylko deklaracja, potrzebna jest jedna definicja . Odpowiednia definicja to:
Na przykład następujące wygenerowałoby błąd:
Podobne uwagi dotyczą funkcji. Zadeklarowanie funkcji bez jej zdefiniowania prowadzi do błędu:
Uważaj, aby zaimplementowana funkcja była dokładnie taka sama, jak zadeklarowana. Na przykład możesz mieć niedopasowane kwalifikatory cv:
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.
źródło
#includes
nie zostały dodane do katalogu źródłowego, również należą do kategorii brakujących definicji.Kolejność określania wzajemnie zależnych bibliotek połączonych jest nieprawidłowa.
Kolejność łączenia bibliotek NIE MA znaczenia, jeśli biblioteki są od siebie zależne. Ogólnie, jeśli biblioteka
A
zależy od bibliotekiB
,libA
MUSI pojawić się wcześniejlibB
w flagach linkera.Na przykład:
Utwórz biblioteki:
Skompilować:
Więc powtórzyć jeszcze raz, kolejność CZY sprawa!
źródło
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”.
Na przykład mamy trochę kodu
i
Twórz pliki obiektowe
Po fazie asemblera mamy plik obiektowy, który zawiera dowolne symbole do wyeksportowania. Spójrz na symbole
Odrzuciłem niektóre wiersze z danych wyjściowych, ponieważ nie mają one znaczenia
Widzimy więc następujące symbole do eksportu.
src2.cpp nic nie eksportuje i nie widzieliśmy żadnych jego symboli
Połącz nasze pliki obiektowe
i uruchom to
Linker widzi wyeksportowane symbole i łączy je. Teraz próbujemy odkomentować wiersze w src2.cpp jak tutaj
i odbuduj plik obiektowy
OK (bez błędów), ponieważ budujemy tylko plik obiektowy, łączenie jeszcze się nie zakończyło. Spróbuj połączyć
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
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ń
do
tzn. powinieneś mieć jak poniżej
zmieniliśmy widoczność local_var_name i ustawiliśmy jego wartość na 456789. Spróbuj zbudować z niego plik obiektowy
ok, patrz readelf wyjście (symbole)
teraz local_var_name ma Bind GLOBAL (wcześniej LOCAL)
połączyć
i uruchom to
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.
źródło
Symbole zostały zdefiniowane w programie C i użyte w kodzie C ++.
Funkcja (lub zmienna)
void foo()
została zdefiniowana w programie C i próbujesz użyć jej w programie C ++:Linker C ++ oczekuje, że nazwy będą zniekształcone, więc musisz zadeklarować funkcję jako:
Odpowiednio, zamiast być zdefiniowanym w programie C, funkcja (lub zmienna)
void foo()
została zdefiniowana w C ++, ale z połączeniem C: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;
źródło
#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.
źródło
Niepoprawne importowanie / eksportowanie metod / klas w modułach / dll (specyficzne dla kompilatora).
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:
Makro
THIS_MODULE
byłoby zdefiniowane tylko w module, który eksportuje funkcję. W ten sposób deklaracja:rozwija się do
i każe kompilatorowi wyeksportować funkcję, ponieważ bieżący moduł zawiera jej definicję. Uwzględniając deklarację w innym module, rozwinąłby się do
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:
źródło
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:
W b.cpp chcemy wywołać funkcję foo, więc dodajemy
aby zadeklarować funkcję foo () i wywołać ją w innym ciele funkcji, powiedz
bar()
: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.źródło
Implementacje szablonu nie są widoczne.
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: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ź .
źródło
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.
źródło
WinMain
. Prawidłowe programy C ++ wymagająmain
.Również jeśli korzystasz z bibliotek stron trzecich, upewnij się, że masz poprawne 32-bitowe pliki binarne
źródło
Microsoft oferuje
#pragma
odwołanie do właściwej biblioteki w czasie łącza;Oprócz ścieżki do biblioteki, w tym katalogu biblioteki, powinna to być pełna nazwa biblioteki.
źródło
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ącpackagename\build\native\packagename.targets
i wewnątrz tego pliku, kopiując wszystkiev110
sekcje. Zmieniłemv110
sięv120
w polach stan tylko będąc bardzo ostrożny, aby zostawić wszystko jak ścieżki nazw plikówv110
. To po prostu pozwoliło Visual Studio 2013 połączyć się z bibliotekami na 2012 rok, w tym przypadku zadziałało.źródł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ą kompilowaneźródło
Błąd w kompilatorze / IDE
Niedawno 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:
źródło
Użyj łącznika, aby zdiagnozować błąd
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 .źródło
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.
źródło
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.
Więc jeśli nagle pojawią się błędy linkera podczas przejścia na GCC po 5.1.0, warto to sprawdzić.
źródło
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:
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
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
źródło
Twoje połączenie zużywa biblioteki przed plikami obiektowymi, które się do nich odnoszą
libfoo
zależy odlibbar
tego, twój link poprawnie wstawialibfoo
wcześniejlibbar
.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 ++
Minimalny przykład dotyczący biblioteki statycznej, którą sam zbudowałeś
my_lib.c
my_lib.h
eg1.c
Budujesz swoją bibliotekę statyczną:
Kompilujesz swój program:
Próbujesz połączyć go z
libmy_lib.a
i nie powiedzie się:Ten sam wynik, jeśli kompilujesz i łączysz w jednym kroku, na przykład:
Minimalny przykład dotyczący wspólnej biblioteki systemowej, biblioteki kompresji
libz
eg2.c
Skompiluj swój program:
Spróbuj połączyć swój program
libz
i zawieść:To samo, jeśli kompilujesz i łączysz za jednym razem:
I wariant na przykładzie 2 obejmujący
pkg-config
:Co robisz źle
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:
Sukces:
Podłącz przykład 2 poprawnie:
Sukces:
Połącz
pkg-config
poprawnie przykładową odmianę 2 :Wyjaśnienie
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 obiektowychlibmy_lib.a
.Jest tylko plik obiektowy
libmy_lib.a
, a mianowiciemy_lib.o
, i jest zdefiniowana tylko jedna rzeczmy_lib.o
, a mianowicie funkcjahw
.Linker zdecyduje, że twój program potrzebuje
my_lib.o
wtedy i tylko wtedy, gdy już wie, że twój program się odwołujehw
, 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 dlahw
.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 dohw
zostały rozwiązane .Podczas próby połączenia programu, takiego jak:
linker nie dodał
eg1.o
do programu, gdy widzi-lmy_lib
. Ponieważ w tym momencie nie widziałemeg1.o
. Twój program nie ma jeszcze dokonywać żadnych odniesień dohw
: 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 zastosowanialibmy_lib.a
.Następnie znajduje
eg1.o
i dodaje do programu. Plik obiektowy w sekwencji łączenia jest zawsze dodawany do programu. Teraz program zawiera odniesieniehw
i nie zawiera definicjihw
; ale w sekwencji łączenia nie pozostało nic, co mogłoby zapewnić brakującą definicję. Odwołanie dohw
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 funkcjizlibVersion
, do której odwołuje się tylko razeg2.c
. Jeśli linker doda to odwołanie do programu, a następnie znajdzie wyeksportowaną definicjęlibz
, odwołanie zostanie rozwiązaneAle kiedy próbujesz połączyć program w następujący sposób:
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ą weg2.o
, czego jeszcze nie widziano. Linker decyduje więc, że nie ma sensulibz
. Kiedy osiągnieeg2.o
, dodaje go do programu, a następnie ma niezdefiniowane odniesieniezlibVersion
, 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:staje się:
co jest znowu tylko przykładem 2.
Mogę odtworzyć problem w przykładzie 1, ale nie w przykładzie 2
Połączenie:
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.
Dlaczego wciąż mam ten problem, nawet jeśli jednocześnie kompiluję i łączę?
Jeśli tylko to zrobię:
z pewnością gcc
eg1.c
najpierw musi się skompilować , a następnie połączyć wynikowy plik obiektowylibmy_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: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:zamiast:
Zobacz też
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.
źródło
Szablony zaprzyjaźnienia ...
Biorąc pod uwagę fragment kodu typu szablonu z operatorem znajomego (lub funkcją);
operator<<
Jest zadeklarowana jako funkcja bez szablonu. Dla każdegoT
używanego typuFoo
musi istnieć szablon bez szablonuoperator<<
. Na przykład, jeśli istniejeFoo<int>
zadeklarowany typ , musi istnieć implementacja operatora w następujący sposób;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;Powyższy kod ogranicza przyjaźń operatora do odpowiedniej instancji
Foo
, tj.operator<< <int>
Instancja jest ograniczona do dostępu do prywatnych członków instancjiFoo<int>
.Alternatywy obejmują;
Umożliwiając przyjaźni objęcie wszystkich instancji szablonów w następujący sposób;
Lub implementacja dla
operator<<
może być wykonana inline wewnątrz definicji klasy;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 ;
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
źródło
Gdy ścieżki dołączania są różne
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ą:
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żywaszheader2.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.
Dalsze opracowanie z przykładem
Mam dwa projekty:
graphics.lib
imain.exe
. Oba projekty zależą odcommon_math.h
. Załóżmy, że biblioteka eksportuje następującą funkcję:A potem dołącz do biblioteki w swoim własnym projekcie.
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ładzievec3
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).Debugowanie linkera
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!
źródło
Niespójne
UNICODE
definicjeKompilacja Windows UNICODE jest budowana z
TCHAR
itp. Zdefiniowanym jakowchar_t
itd. Gdy nie buduje się zUNICODE
definicją zdefiniowaną jako kompilacjaTCHAR
zdefiniowana jakochar
itd. TeUNICODE
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óryUNICODE
nie jest zdefiniowany, spowoduje błędy linkera, ponieważ wystąpi niedopasowanie definicjiTCHAR
;char
vswchar_t
.Błąd zwykle obejmuje funkcję o wartości typu pochodnego
char
lubwchar_t
, mogą obejmować równieżstd::basic_string<>
itp. Podczas przeglądania funkcji dotkniętego w kodzie, to często będzie odniesienie doTCHAR
lubstd::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;
Lub w ustawieniach projektu;
Lub w wierszu poleceń;
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”.
źródło
Oczyść i odbuduj
„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).
źródło
Brak „extern” w
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ślnieextern
(tzn. Gdystatic
brakuje słowa kluczowego).Przykład:
poprawne byłoby użycie pliku nagłówka i dołączenie go do pliku2.cpp i pliku1.cpp
Alternatywnie można zadeklarować
const
zmienną w pliku1.cpp jawnieextern
źródło
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.
Różne wersje bibliotek
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: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.oW 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
Rozkład
Aby to naprawić, po prostu musiałem zmienić <eksperymentalny :: system plików> w pliku.h na <system plików> .
źródło
Podczas łączenia z bibliotekami współdzielonymi, upewnij się, że użyte symbole nie są ukryte.
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:
ukryte / lokalne symbole są oznaczone
nm
małymi literami, na przykładt
zamiast `T dla sekcji kodu: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:Co w przybliżeniu odpowiada wersji Windows / MSVC:
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 literaminm
) 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:
źródło
nm -CD
lubnm -gCD
do przeglądania zewnętrznych symboli. Zobacz także Widoczność na wiki GCC.Różne architektury
Może zostać wyświetlony komunikat:
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
/, wlib64
którym biblioteka jest pobierana.źródło