g ++ niezdefiniowane odniesienie do typeinfo

208

Właśnie natrafiłem na następujący błąd (i znalazłem rozwiązanie online, ale nie występuje ono w przepełnieniu stosu):

(.gnu.linkonce. [stuff]): niezdefiniowane odniesienie do [metody] [plik obiektowy] :(. gnu.linkonce. [stuff]): niezdefiniowane odniesienie do `typeinfo for [classname] '

Dlaczego można dostać jeden z tych błędów linkera „niezdefiniowane odniesienie do typeinfo”?

(Punkty bonusowe, jeśli potrafisz wyjaśnić, co dzieje się za kulisami.)

cdleary
źródło
31
Wiem, że to stary post, ale miałem dzisiaj ten sam problem, a rozwiązaniem było po prostu zdefiniowanie mojej funkcji wirtualnej jako wirtualnej abc () {} w klasie podstawowej, zamiast wirtualnej abc (); co dało błąd.
Nav
15
lepiej jeszcze jako virtual void abc() =0;(jeśli wersja podstawowa nigdy nie jest wywoływana)
dhardy
3
@Nav: Jeśli zdefiniujesz w abc()ten sposób, możesz łatwo zapomnieć o przedefiniowaniu abc()w klasie pochodnej i pomyśleć, że wszystko jest w porządku, ponieważ nadal będziesz mógł wywoływać tę funkcję bez żadnego problemu. Dobra praktyka implementacji funkcji czysto wirtualnych znajduje się w tym artykule i polega na tym, aby funkcja wypisała „czystą funkcję wirtualną o nazwie”, a następnie zawiesiła program.
HelloGoodbye
1
miałem ten sam błąd. Odkryłem, że zmiana kolejności odwołań do „lib” może pomóc. właśnie przeniosłem bibliotekę problemów z początku na koniec listy i to rozwiązało problem
javapowered
2
GAH. To jest teraz przynajmniej drugi raz, kiedy nawigowałem dokładnie na tej stronie, aby przeczytać komentarz @dhardy i powiedzieć sobie „Doh”. Spędziłem 45 minut próbując wyśledzić jakieś szalone zachowanie i wszystko, czego potrzebowałem, to = 0;.
dwanderson

Odpowiedzi:

222

Jednym z możliwych powodów jest to, że deklarujesz funkcję wirtualną bez jej definiowania.

Kiedy deklarujesz to bez definiowania go w tej samej jednostce kompilacji, oznacza to, że jest on zdefiniowany gdzie indziej - oznacza to, że faza linkera spróbuje go znaleźć w jednej z innych jednostek kompilacji (lub bibliotek).

Przykładem zdefiniowania funkcji wirtualnej jest:

virtual void fn() { /* insert code here */ }

W takim przypadku dołączasz definicję do deklaracji, co oznacza, że ​​linker nie musi jej później rozwiązywać.

Linia

virtual void fn();

deklaruje fn()bez jego zdefiniowania i spowoduje komunikat o błędzie, o który pytasz.

Jest bardzo podobny do kodu:

extern int i;
int *pi = &i;

który stwierdza, że ​​liczba całkowita ijest zadeklarowana w innej jednostce kompilacji, którą należy rozwiązać w czasie łączenia (w przeciwnym razie pinie można ustawić jej adresu).

paxdiablo
źródło
28
Błędne jest twierdzenie, że virtual void fn() = 0jest to definicja. To nie jest definicja, ale zwykła deklaracja . Jedynym powodem, dla którego linker nie próbuje go rozwiązać, jest to, że odpowiedni wpis VMT nie będzie odwoływał się do treści funkcji (najprawdopodobniej będzie zawierał wskaźnik zerowy). Jednak nikt nie zabrania ci wywoływania tej czystej funkcji wirtualnej w sposób nie-wirtualny, tj. Przy użyciu w pełni kwalifikowanej nazwy. W tym przypadku łącznik będzie wyglądać dla ciała, i trzeba będzie zdefiniować funkcję. I tak, można zdefiniować ciało czystej funkcji wirtualnej.
ANT
1
A czasem trzeba nawet zadeklarować ciało dla czystej funkcji wirtualnej.
znak
3
Kompilator (g ++) powie ci, jaki jest brakujący symbol. Uwaga: W przypadku dynamicznego łączenia bibliotek możesz uzyskać zniekształconą nazwę. Użyj c ++ filt <mangledNameVariable>, aby uzyskać go w czytelnej formie. Błąd typeinfo z nazwą klasy był w moim przypadku z powodu braku implementacji wirtualnego destruktora w niektórych klasach podstawowych.
chmike
1
Pytanie wyraźnie wspomina, że ​​brakuje informacji typu typeinfo, która ma związek z rtti. Zobacz komentarz Damona w stackoverflow.com/questions/11904519/...
wilsonmichaelpatrick
1
@gbmhunter, wystarczy. Dokonał zmiany.
paxdiablo
149

Może się to zdarzyć również podczas miksowania -fno-rttii -frttikodowania. Następnie musisz upewnić się, że każda klasa, która type_infojest dostępna w -frttikodzie, ma skompilowaną metodę klucza -frtti. Taki dostęp może nastąpić po utworzeniu obiektu klasy, użyciu dynamic_castitp.

[ źródło ]

Sergiy Belozorov
źródło
20
DZIĘKUJĘ BARDZO. To rozwiązało mój problem po 5 godzinach wyszukiwania.
steipete
1
link źródłowy jest martwy, z pewnością był taki sam jak permalink.gmane.org/gmane.comp.gcc.help/32475
matematyka
1
Dzięki za zwrócenie na to uwagi. Oryginalna strona jest nadal dostępna tutaj: web.archive.org/web/20100503172629/http://www.pubbs.net/201004/…
Sergiy
3
StackOverflow.com ponownie na ratunek! Chciałbym móc głosować więcej niż raz. Po godzinie walenia głową w klawiaturę potrzebowałem twojej odpowiedzi.
spartygw
1
n + 1 życie uratowane i wciąż się liczy :)
Gabriel
53

Dzieje się tak, gdy w zadeklarowanych (nieczystych) funkcjach wirtualnych brakuje obiektów. W twojej definicji klasy coś takiego:

virtual void foo();

Należy zdefiniować (wbudowany lub w połączonym pliku źródłowym):

virtual void foo() {}

Lub zadeklarowany jako czysty wirtualny:

virtual void foo() = 0;
cdleary
źródło
27

Cytowanie z podręcznika gcc :

W przypadku klas polimorficznych (klas z funkcjami wirtualnymi) obiekt type_info jest zapisywany razem z vtable [...] W przypadku wszystkich innych typów zapisujemy obiekt type_info, gdy jest używany: przy zastosowaniu `typeid 'do wyrażenia, rzucanie obiektu lub odwoływanie się do typu w klauzuli catch lub specyfikacji wyjątku.

I nieco wcześniej na tej samej stronie:

Jeśli klasa zadeklaruje jakiekolwiek nie-wbudowane, nieczyste funkcje wirtualne, pierwsza zostanie wybrana jako „metoda klucza” dla klasy, a vtable jest emitowana tylko w jednostce tłumaczenia, w której zdefiniowano metodę klucza.

Tak więc ten błąd występuje, gdy „kluczowa metoda” nie ma swojej definicji, jak już wspomniano w innych odpowiedziach.

CesarB
źródło
2
W moim przypadku miałem klasę podstawową, która zadeklarowała, ale nie zdefiniowała metod wirtualnych, które nie były czysto wirtualne. Gdy stworzyłem je czysto wirtualne, co miałem na myśli, błędy linkera zniknęły.
Tatiana Racheva
@TatianaRacheva Thanks! Zgłaszanie błędów przez linker jest mniej niż pomocne, a dla dużego interfejsu bardzo łatwo przeoczyć brak „= 0;” dla czystej wirtualnej!
rholmes
20

Jeśli łączysz jeden .so z drugim, jeszcze jedną możliwością jest kompilacja z „-fvisibility = hidden” w gcc lub g ++. Jeśli oba. Dla linkera wygląda to na niezaimplementowaną funkcję wirtualną (jak w odpowiedziach paxdiablo i cdleary).

W takim przypadku musisz zrobić wyjątek dla widoczności klasy podstawowej za pomocą

__attribute__ ((visibility("default")))

w deklaracji klasowej. Na przykład,

class __attribute__ ((visibility("default"))) boom{
    virtual void stick();
}

Innym rozwiązaniem jest oczywiście nieużywanie „-fvisibility = hidden”. To komplikuje kompilator i linker, być może ze szkodą dla wydajności kodu.

człowiek
źródło
1
Nie musisz eksportować (odkrywać) klasy podstawowej, jeśli jest ona abstrakcyjna lub nieużywana, tylko funkcje niebędące wirtualnymi, zwykle tylko konstruktor. Natomiast klasy pochodne muszą zostać wyeksportowane, jeśli są używane.
Chris Huang-Leaver,
czuję się jak hack, ale to rozwiązało objawy po mojej stronie. Dzięki !
Poniedziałek
15

Poprzednie odpowiedzi są poprawne, ale ten błąd może być również spowodowany próbą użycia typeid na obiekcie klasy, który nie ma funkcji wirtualnych. C ++ RTTI wymaga vtable, więc klasy, na których chcesz przeprowadzić identyfikację typu, wymagają co najmniej jednej funkcji wirtualnej.

Jeśli chcesz, aby informacje o typie działały na klasie, dla której tak naprawdę nie chcesz żadnych funkcji wirtualnych, ustaw wirtualny destruktor.

Tyler McHenry
źródło
2
Zmodernizowany, ponieważ myślę, że jest to bardziej prawdopodobne, że jest to przyczyną tego konkretnego komunikatu o błędzie (w przeciwieństwie do bardziej ogólnego przypadku niezdefiniowanych metod ...)
Alastair
4
Jedną z rzeczy, do których musiałem się przyzwyczaić z SO, nie jest odniesienie do „powyższych” odpowiedzi, ponieważ kolejność może się zmieniać w zależności od głosów. Zwykle nie odnoszę się do żadnych innych odpowiedzi, ponieważ można je również usunąć. Uważam, że odpowiedzi powinny być samodzielne. Nadal jednak odnoszę się do nazw użytkowników w celu przypisania.
paxdiablo
Możesz użyć typeid bez vtable; zobacz moją odpowiedź na cytaty z podręcznika gcc.
CesarB,
11

Właśnie spędziłem kilka godzin na tym błędzie i chociaż inne odpowiedzi tutaj pomogły mi zrozumieć, co się dzieje, nie rozwiązały mojego konkretnego problemu.

Pracuję nad projektem, który kompiluje się przy użyciu obu clang++i g++. Nie miałem problemów z łączeniem się clang++, ale otrzymywałem undefined reference to 'typeinfo forbłąd g++.

Ważna kwestia: Powiązanie zamówienia ma znaczenie g++. Jeśli podasz biblioteki, które chcesz połączyć w niepoprawnej kolejności, możesz uzyskaćtypeinfo błąd.

Zobacz to pytanie SO, aby uzyskać więcej informacji na temat łączenia zamówienia z gcc/ g++.

dinkelk
źródło
Dziękuję Ci!!! Spędziłem ponad dzień, próbując dowiedzieć się, dlaczego dostaję ten błąd i nic nie działało, dopóki nie zobaczyłem tej odpowiedzi i tej, z którą się łączyłeś. Dzięki wielkie!!
Irene
10

Możliwe rozwiązania dla kodu obsługującego biblioteki RTTI i inne niż RTTI:

a) Zrekompiluj wszystko za pomocą opcji -frtti lub -fno-rtti
b) Jeśli a) nie jest dla Ciebie możliwe, spróbuj wykonać następujące czynności:

Załóżmy, że libfoo jest zbudowany bez RTTI. Twój kod używa libfoo i kompiluje się z RTTI. Jeśli użyjesz klasy (Foo) w libfoo, która ma wirtuale, najprawdopodobniej napotkasz błąd czasu łącza, który mówi: brak typu info dla klasy Foo.

Zdefiniuj inną klasę (np. FooAdapter), która nie ma wirtualnego i będzie przekierowywać wywołania do używanego Foo.

Skompiluj FooAdapter w małej statycznej bibliotece, która nie używa RTTI i zależy tylko od symboli libfoo. Podaj dla niego nagłówek i użyj go zamiast tego w kodzie (który używa RTTI). Ponieważ FooAdapter nie ma funkcji wirtualnej, nie będzie miał żadnych informacji typu i będziesz mógł połączyć swój plik binarny. Jeśli używasz wielu różnych klas z libfoo, to rozwiązanie może nie być wygodne, ale jest to początek.

Francois
źródło
To było dla mnie, linkowanie do biblioteki z różnymi ustawieniami RTTI.
bagno
6

Podobnie jak w powyższej dyskusji RTTI, NO-RTTI, ten problem może również wystąpić, jeśli użyjesz dynamicznej wersji i nie podasz kodu obiektowego zawierającego implementację klasy.

Wystąpił ten problem, budując na Cygwin, a następnie przenosząc kod na Linuksa. Pliki make, struktura katalogów, a nawet wersje gcc (4.8.2) były identyczne w obu przypadkach, ale kod łączył się i działał poprawnie na Cygwin, ale nie mógł połączyć się z Linuksem. Red Hat Cygwin najwyraźniej dokonał modyfikacji kompilatora / linkera, które unikają wymogu łączenia kodu obiektowego.

Komunikat o błędzie linkera Linuksa poprawnie skierował mnie do linii dynamic_cast, ale wcześniejsze komunikaty na tym forum sprawiły, że szukałem brakujących implementacji funkcji, a nie rzeczywistego problemu: brakującego kodu obiektu. Moim obejściem było zastąpienie funkcji typu wirtualnego w klasie bazowej i pochodnej, np. Virtual int isSpecialType (), zamiast używania dynamic_cast. Ta technika pozwala uniknąć wymogu łączenia kodu implementacji obiektu tylko po to, aby Dynamic_cast działał poprawnie.

FNE
źródło
5

W klasie bazowej (abstrakcyjnej klasie bazowej) deklarujesz wirtualny destruktor, a ponieważ nie możesz zadeklarować destruktora jako czystej funkcji wirtualnej, musisz zdefiniować go tutaj w klasie abstrakcyjnej, po prostu fikcyjna definicja wirtualna ~ baza ( ) {} zrobi lub w dowolnej klasie pochodnej.

Jeśli tego nie zrobisz, w czasie połączenia skończysz jako „niezdefiniowany symbol”. Ponieważ VMT ma wpis dla wszystkich czysto wirtualnych funkcji z pasującym NULL, ponieważ aktualizuje tabelę w zależności od implementacji w klasie pochodnej. Ale w przypadku nieoczyszczonych, ale wirtualnych funkcji, potrzebuje definicji w czasie połączenia, aby mógł zaktualizować tabelę VMT.

Użyj c ++ filt, aby rozplątać symbol. Jak $ c ++ filt _ZTIN10storageapi8BaseHostE wyświetli coś w rodzaju „typeinfo for storageapi :: BaseHost”.

Prashanth
źródło
3

Właśnie otrzymałem wiele z tych błędów. Stało się to, że podzieliłem klasę zawierającą tylko pliki nagłówkowe na plik nagłówkowy i plik cpp. Jednak nie zaktualizowałem mojego systemu kompilacji, więc plik CPP nie został skompilowany. Oprócz zwykłych niezdefiniowanych odwołań do funkcji zadeklarowanych w nagłówku, ale niezaimplementowanych, mam wiele takich błędów typu info.

Rozwiązaniem było ponowne uruchomienie systemu kompilacji w celu skompilowania i połączenia nowego pliku CPP.

Claudiu
źródło
3

w moim przypadku użyłem biblioteki innej firmy z plikami nagłówkowymi, a więc i plikami. podklasowałem jedną klasę, a błąd połączenia wystąpił podczas próby utworzenia instancji mojej podklasy.

jak wspomniała @sergiy, wiedząc, że może to być problem „rtti”, udało mi się to obejść, umieszczając implementację konstruktora w osobnym pliku .cpp i stosując do pliku flagi kompilacji „-fno-rtti” . to dobrze działa.

ponieważ nadal nie jestem całkiem pewien, jaki jest wewnętrzny błąd tego łącza, nie jestem pewien, czy moje rozwiązanie jest ogólne. myślę jednak, że warto spróbować przed próbą użycia adaptera, o czym wspomniał @francois. i oczywiście, jeśli wszystkie kody źródłowe są dostępne (nie w moim przypadku), lepiej skompiluj ponownie z '-frtti', jeśli to możliwe.

jeszcze jedno, jeśli zdecydujesz się wypróbować moje rozwiązanie, spróbuj uczynić oddzielny plik tak prostym, jak to możliwe, i nie używaj fantazyjnych funkcji C ++. zwracaj szczególną uwagę na rzeczy związane z doładowaniem, ponieważ wiele z nich zależy od rtti.

uwydoc
źródło
2

Mam ten sam błąd, gdy mój interfejs (ze wszystkimi czystymi funkcjami wirtualnymi) potrzebował jeszcze jednej funkcji i zapomniałem ją „zerować”.

miałem

class ICommProvider { public: /** * @brief If connection is established, it sends the message into the server. * @param[in] msg - message to be send * @return 0 if success, error otherwise */ virtual int vaSend(const std::string &msg) = 0; /** * @brief If connection is established, it is waiting will server response back. * @param[out] msg is the message received from server * @return 0 if success, error otherwise */ virtual int vaReceive(std::string &msg) = 0; virtual int vaSendRaw(const char *buff, int bufflen) = 0; virtual int vaReceiveRaw(char *buff, int bufflen) = 0; /** * @bief Closes current connection (if needed) after serving * @return 0 if success, error otherwise */ virtual int vaClose(); };

Ostatnia wersja vaClose nie jest wirtualna, więc skompilowana nie wiedziała, gdzie uzyskać dla niej implementację i dlatego się pomyliła. moja wiadomość była:

... TCPClient.o :(. Rodata + 0x38): niezdefiniowane odniesienie do `typeinfo for ICommProvider '

Prosta zmiana z

virtual int vaClose();

do

virtual int vaClose() = 0;

naprawiono problem. mam nadzieję, że to pomoże

Alex Paniutin
źródło
1

Występuje rzadka sytuacja, ale może to pomóc innym przyjaciołom w podobnej sytuacji. Muszę pracować na starszym systemie z gcc 4.4.7. Muszę skompilować kod z obsługą c ++ 11 lub nowszą, więc buduję najnowszą wersję gcc 5.3.0. Podczas budowania mojego kodu i łączenia z zależnościami, jeśli zależność jest budowana za pomocą starszego kompilatora, dostałem błąd „niezdefiniowane odniesienie do” błędu, mimo że jasno zdefiniowałem ścieżkę łączenia za pomocą opcji -L / path / to / lib -llibname. Niektóre pakiety, takie jak boost i projekty budowane za pomocą cmake, zwykle mają tendencję do używania starszego kompilatora i zwykle powodują takie problemy. Musisz przejść długą drogę, aby upewnić się, że korzystają z nowszego kompilatora.

Kemin Zhou
źródło
1

W moim przypadku jest to wyłącznie kwestia zależności biblioteki, nawet jeśli mam wywołanie dynamic_cast. Po dodaniu wystarczającej zależności do pliku makefile problem zniknął.

Charlie
źródło
0

Sprawdź, czy Twoje zależności zostały skompilowane bez -f-nortti .

W przypadku niektórych projektów musisz to ustawić jawnie, na przykład w RocksDB:

USE_RTTI=1 make shared_lib -j4
Witalij Isaev
źródło
0

W moim przypadku była to funkcja wirtualna w klasie interfejsu, która nie została zdefiniowana jako czysta wirtualna.

class IInterface
{
public:
  virtual void Foo() = 0;
}

= 0Trochę zapomniałem .

Gęsia skórka
źródło