g ++ невизначене посилання на typeinfo


208

Я щойно зіткнувся з наступною помилкою (і знайшов рішення в Інтернеті, але його немає в переповненні стека):

(.gnu.linkonce. [stuff]): невизначене посилання на [метод] [файл файлу] :(. gnu.linkonce. [речі]): невизначене посилання на `typeinfo для [classname] '

Чому можна отримати одну з цих помилок у зв’язці "неозначеної посилання на typeinfo"?

(Бонусні бали, якщо ви можете пояснити, що відбувається за лаштунками.)


31
Я знаю, що це стара публікація, але у мене була така ж проблема і сьогодні, і рішенням було просто визначити мою віртуальну функцію як віртуальну abc () {} в базовому класі, а не віртуальну abc (); що дало помилку.
Nav

15
ще краще як virtual void abc() =0;(якщо базову версію ніколи не називати)
dhardy

3
@Nav: Якщо ви визначитеся abc()так, ви можете легко забути переосмислити abc()у похідному класі і подумати, що все в порядку, оскільки ви все одно можете без проблем викликати функцію. У цій статті ви знайдете належну практику реалізації чистих віртуальних функцій , і це полягає в тому, щоб функція надрукувала "Чиста віртуальна функція називається", а потім вийшла з ладу програма.
HelloGoodbye

1
у мене була така ж помилка. Я виявив, що зміна порядку посилань на "lib" може допомогти. я щойно перемістив проблему "lib's" з початкової в кінці списку, і це вирішило проблему
javapowered

2
GAH. Це, принаймні, вдруге я перейшов саме на цю сторінку, щоб прочитати коментар @dhardy і сказати собі "До". Щойно витратив 45 хвилин, намагаючись відстежити деяку шалену поведінку, і все, що мені було потрібно = 0;.
dwanderson

Відповіді:


222

Однією з можливих причин є те, що ви декларуєте віртуальну функцію, не визначаючи її.

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

Прикладом визначення віртуальної функції є:

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

У цьому випадку ви додаєте визначення до декларації, що означає, що лінкеру не потрібно його вирішувати пізніше.

Лінія

virtual void fn();

декларує, fn()не визначаючи його, і спричинить повідомлення про помилку, про яке ви запитали.

Він дуже схожий на код:

extern int i;
int *pi = &i;

який зазначає, що ціле число iоголошено в іншому блоці компіляції, який повинен бути вирішений під час посилання (інакше piне можна встановити його адресу).


28
Невірно сказати, що virtual void fn() = 0це визначення. Це не визначення, а проста декларація . Єдина причина, що лінкер не намагається вирішити, це те, що відповідний запис VMT не буде посилатися на функціональне тіло (буде містити нульовий покажчик, швидше за все). Однак ніхто не забороняє вам називати цю чисту віртуальну функцію невіртуальним способом, тобто використовуючи повністю кваліфіковане ім’я. В цьому випадку компоновщик буде шукати тіло, і ви повинні визначити функцію. І так, ви можете визначити тіло для чистої віртуальної функції.
AnT

1
І іноді потрібно навіть оголосити тіло для чистої віртуальної функції.
позначити

3
Компілятор (g ++) підкаже, що є пропущеним символом. Примітка. У разі динамічного посилання на бібліотеку ви можете отримати ім'я, яке не має права. Використовуйте c ++ filt <mangledNameVariable>, щоб отримати його в читаному вигляді. Помилка typeinfo з назвою класу була в моєму випадку через відсутність реалізації віртуального деструктора в якомусь базовому класі.
chmike

1
У цьому питанні конкретно зазначається, що тип інформації відсутній, що стосується rtti. Дивіться коментар від Деймона в stackoverflow.com/questions/11904519 / ...
wilsonmichaelpatrick

1
@gbmhunter, досить справедливо. Внесли зміни.
paxdiablo

149

Це також може статися при змішуванні -fno-rttiта -frttiкодуванні. Тоді вам потрібно переконатися, що будь-який клас, до якого type_infoзвертаються до -frttiкоду, має свій ключовий метод, зібраний із -frtti. Такий доступ може статися під час створення об’єкта класу, використання dynamic_castтощо.

[ джерело ]


20
ДУЖЕ ДЯКУЮ. Це вирішило мою проблему після 5 годин пошуку.
steipete

1
джерело посилання мертвий, це було точно так само, як permalink.gmane.org/gmane.comp.gcc.help/32475
математика

1
Дякуємо, що вказали на це. Оригінальна сторінка все ще доступна тут: web.archive.org/web/20100503172629/http://www.pubbs.net/201004/…
Сергій

3
StackOverflow.com знову на допомогу! Я б хотів, щоб я міг подати заявку не раз. Після удару головою по клавіатурі протягом години ваша відповідь була тим, що мені потрібно.
spartygw

1
n + 1 життя врятували і досі рахують :)
Габріель

53

Це відбувається, коли оголошені (нечисті) віртуальні функції відсутні органи. У визначенні вашого класу щось на кшталт:

virtual void foo();

Слід визначити (вбудований або у пов'язаному вихідному файлі):

virtual void foo() {}

Або оголошено чистим віртуальним:

virtual void foo() = 0;

27

Цитуючи посібник з gcc :

Для поліморфних класів (класів з віртуальними функціями) об’єкт type_info виписується разом з vtable [...] Для всіх інших типів ми виписуємо об'єкт type_info, коли він використовується: при застосуванні `typeid 'до виразу, метання об'єкта або посилання на тип у пункті вилучення або специфікації винятків.

І трохи раніше на тій же сторінці:

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

Отже, ця помилка трапляється, коли «ключовому методу» відсутнє його визначення, як і інші відповіді, про які вже говорилося.


2
У моєму випадку у мене був базовий клас, який декларував, але не визначав віртуальні методи, які не були чистими віртуальними. Одного разу я зробив їх чистими віртуальними, про що я і мав на увазі, помилки лінкера відійшли.
Тетяна Рачева

@TatianaRacheva Дякую! Повідомлення про помилки від лінкера є менш ніж корисним, і для великого інтерфейсу дуже просто упустити відсутність '= 0;' для чистого віртуального!
rholmes

20

Якщо ви пов'язуєте один .soso з іншим, ще одна можливість - компілювання з "-fvisibility = hidden" в gcc або g ++. Якщо обидва .so файли були побудовані з "-fvisibility = hidden" і ключовий метод не є таким же .so, як інша реалізація віртуальної функції, останній не побачить vtable або typeinfo першої. Для лінкера це виглядає як безреалізована віртуальна функція (як у відповідях paxdiablo та cdleary).

У цьому випадку ви повинні зробити виняток для видимості базового класу за допомогою

__attribute__ ((visibility("default")))

в класі декларації. Наприклад,

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

Іншим рішенням, звичайно, є не використовувати "-fvisibility = hidden". Це ускладнює речі для компілятора та лінкера, можливо, на шкоду продуктивності коду.


1
Вам не потрібно експортувати (приховувати) базовий клас, якщо він абстрактний або невикористаний, лише невіртуальні функції, як правило, лише конструктор. З іншого боку, похідні класи повинні бути експортовані, якщо вони використовуються.
Кріс Хуанг-Лівер

відчуває себе злом, але це вирішило симптоми з мого боку. Дякую !
malat

15

Попередні відповіді правильні, але ця помилка також може бути викликана спробою використання typeid на об'єкті класу, який не має віртуальних функцій. C ++ RTTI вимагає vtable, тому для класів, для яких потрібно виконати ідентифікацію типу, потрібна хоча б одна віртуальна функція.

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


2
Оновлено, тому що я думаю, що це швидше є причиною конкретного повідомлення про помилку (на відміну від більш загального випадку невизначених методів ...)
Alastair

4
Одне, до чого я повинен був звикнути, - це не посилання на відповіді "вище", оскільки порядок може змінюватися на основі голосів. Зараз зазвичай не посилаюся на будь-які інші відповіді, оскільки їх також можна видалити. Я вважаю, що відповіді мають бути окремими. Я все ще називаю імена користувачів для атрибуції.
paxdiablo

Ви можете використовувати typeid без vtable; дивіться мою відповідь щодо цитат з посібника з gcc.
CesarB

11

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

Я працюю над проектом, який компілюється з використанням обох clang++та g++. У мене не було проблем із зв’язуванням clang++, але я отримував undefined reference to 'typeinfo forпомилку g++.

Важливий момент: Зв'язування порядку МАТЕРИ з g++. Якщо ви перерахуєте бібліотеки, до яких потрібно зв’язатись, у неправильному порядку, ви можете отримати typeinfoпомилку.

Дивіться це питання ТА для отримання більш детальної інформації про зв'язок замовлення з gcc/ g++.


Дякую!!! Я витратив більше доби, намагаючись з’ясувати, чому я отримую цю помилку, і нічого не вийшло, поки я не побачив цю відповідь і ту, з якою ви пов’язали. Дуже дякую!!
Ірен

10

Можливі рішення для коду, що стосується бібліотек RTTI та не-RTTI:

а) Перекомпілюйте все з -frtti або -fno-rtti
b) Якщо а) для вас неможливо, спробуйте наступне:

Припустимо, що lbfoo побудований без RTTI. Ваш код використовує libfoo і компілюється з RTTI. Якщо ви використовуєте клас (Foo) у libfoo, який має віртуали, ви, ймовірно, зіткнетеся з помилкою в часі зв’язку, яка говорить: відсутній typeinfo для класу Foo.

Визначте інший клас (наприклад, FooAdapter), у якого немає віртуального, і буде переадресовувати дзвінки до Foo, який ви використовуєте.

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


Це було для мене, посилання на бібліотеку з різними налаштуваннями RTTI.
болото

6

Подібно до обговорення RTTI, NO-RTTI, описаного вище, ця проблема також може виникнути, якщо ви використовуєте динамический_cast і не включите об'єктний код, що містить реалізацію класу.

Я зіткнувся з цією проблемою на Cygwin, а потім переніс код у Linux. Файли make, структура каталогів і навіть gcc версії (4.8.2) були ідентичними в обох випадках, але код зв'язаний і працював правильно на Cygwin, але не вдалося підключитися в Linux. Red Hat Cygwin, очевидно, вніс зміни компілятора / лінкера, які уникають вимоги пов'язування об'єктного коду.

Повідомлення про помилку лінкера Linux належним чином спрямовувало мене до рядка динамічної передачі, але попередні повідомлення на цьому форумі змушували мене шукати відсутніх реалізацій функції, а не фактичну проблему: відсутній об'єктний код. Моє вирішення полягало в тому, щоб замінити функцію віртуального типу в базовому та похідному класі, наприклад, virtual int isSpecialType (), а не використовувати dynamic_cast. Ця методика дозволяє уникнути вимоги пов’язати код реалізації об'єкта лише для того, щоб динамічний_каст працював належним чином.


5

У базовому класі (абстрактний базовий клас) ви оголошуєте віртуальний деструктор, і оскільки ви не можете оголосити деструктора чистою віртуальною функцією, ви також повинні визначити його прямо тут в абстрактному класі, а лише фіктивне визначення типу virtual ~ base ( ) {} буде виконано, або в будь-якому з похідних класів.

Якщо цього не зробити, то під час посилання ви опинитесь "невизначеним символом". Оскільки VMT має запис для всіх чистих віртуальних функцій із відповідним NULL, оскільки він оновлює таблицю залежно від реалізації у похідному класі. Але для нечистих, але віртуальних функцій воно потребує визначення під час посилання, щоб воно могло оновити таблицю VMT.

Використовуйте c ++ filt для демонтажу символу. Як $ c ++ filt _ZTIN10storageapi8BaseHostE видасть щось на кшталт "typeinfo for storageapi :: BaseHost".


3

Я отримав дуже багато цих помилок саме зараз. Що сталося, це те, що я розділив клас, призначений лише для заголовка, на файл заголовка та файл cpp. Однак я не оновив систему складання, тому файл cpp не скомпілювався. Серед просто невизначених посилань на функції, оголошені в заголовку, але не реалізовані, я отримав багато таких помилок typeinfo.

Рішення полягало в тому, щоб запустити систему збирання для компіляції та посилання нового файлу cpp.


3

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

як згадував @sergiy, знаючи, що це може бути проблема "rtti", мені вдалося вирішити це, поставивши реалізацію конструктора в окремий файл .cpp та застосувати до файлу прапорці компіляції "-fno-rtti". . це добре працює.

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

Ще одна річ, якщо ви вирішили спробувати моє рішення, спробуйте зробити окремий файл максимально простим, і не використовуйте деякі фантазійні функції C ++. приділіть особливу увагу питанням, пов'язаним із підвищенням якості, адже багато чого залежить від rtti.


2

У мене така ж помилка, коли мій інтерфейс (з усіма чистими віртуальними функціями) потребував ще однієї функції, і я забув її "обнулити".

я мав

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

Останнє vaClose не віртуальне, тому компільований не знав, де його реалізувати, і тим самим заплутався. моє повідомлення було:

... TCPClient.o :(. Rodata + 0x38): невизначене посилання на `typeinfo для ICommProvider '

Проста зміна на

virtual int vaClose();

до

virtual int vaClose() = 0;

виправили проблему. сподіваюся, що це допомагає


1

Я стикаюся з ситуацією, яка рідкісна, але це може допомогти іншим друзям у подібній ситуації. Мені потрібно працювати над старішою системою з gcc 4.4.7. Я маю компілювати код з підтримкою c ++ 11 або вище, тому я будую останню версію gcc 5.3.0. Під час створення мого коду та прив'язки до залежностей, якщо залежність будується зі старшого компілятора, я отримав помилку "невизначеного посилання на", хоча я чітко визначив шлях зв’язку із -L / path / to / lib -llibname. Деякі пакети, такі як boost та проекти, побудовані за допомогою cmake, зазвичай мають тенденцію використовувати старіший компілятор, і вони зазвичай викликають такі проблеми. Вам потрібно пройти довгий шлях, щоб переконатися, що вони використовують новіший компілятор.


1

У моєму випадку це виключно проблема залежності від бібліотеки, навіть якщо у мене є дзвінок з динамічною передачею. Після додавання достатньої залежності в makefile цієї проблеми не було.


0

Перевірте, чи були складені ваші залежності без -f-nortti.

Для деяких проектів ви повинні встановити це чітко, як у RocksDB:

USE_RTTI=1 make shared_lib -j4

0

У моєму випадку це була віртуальна функція в інтерфейсному класі, який не визначався як чистий віртуальний.

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

Я = 0трохи забув .

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