g ++ неопределенная ссылка на typeinfo

209

Я просто наткнулся на следующую ошибку (и нашел решение в сети, но его нет в переполнении стека):

(.gnu.linkonce. [stuff]): неопределенная ссылка на [метод] [объектный файл] :(. gnu.linkonce. [stuff]): неопределенная ссылка на `typeinfo для [classname] '

Почему можно получить одну из этих ошибок компоновщика «неопределенная ссылка на typeinfo»?

(Бонусные баллы, если вы можете объяснить, что происходит за кулисами.)

cdleary
источник
31
Я знаю, что это старый пост, но у меня была та же проблема сегодня, и решение было просто определить мою виртуальную функцию как виртуальную abc () {} в базовом классе вместо виртуальной abc (); который дал ошибку.
Nav
15
еще лучше virtual void abc() =0;( как если бы базовая версия никогда не называлась)
хард
3
@Nav: Если вы определите abc()таким образом, вы можете легко забыть переопределить abc()в производном классе и подумать, что все в порядке, так как вы все равно можете вызывать функцию без проблем. Хорошая практика для реализации чисто виртуальных функций найдена в этой статье , и она состоит в том, чтобы заставить функцию печатать «Вызов чисто виртуальной функции», а затем завершить работу программы.
Здравствуйте, до свидания
1
у меня была та же ошибка. Я обнаружил, что изменение порядка ссылок на «lib» может помочь. я просто переместил проблему lib с самого начала в конец списка, и это решило проблему
javapowered
2
GAH. Теперь я, по крайней мере, во второй раз перехожу именно на эту страницу, чтобы прочитать комментарий @dhardy и сказать себе «Дох». Просто потратил 45 минут, пытаясь отследить какое-то сумасшедшее поведение, и все, что мне нужно было = 0;.
dwanderson

Ответы:

223

Одна из возможных причин заключается в том, что вы объявляете виртуальную функцию, не определяя ее.

Когда вы объявляете это, не определяя его в том же модуле компиляции, вы указываете, что он определен где-то еще - это означает, что фаза компоновщика будет пытаться найти его в одном из других модулей компиляции (или библиотек).

Пример определения виртуальной функции:

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

В этом случае вы присоединяете определение к объявлению, что означает, что компоновщику не нужно разрешать его позже.

Линия

virtual void fn();

объявляет fn()без определения и вызовет сообщение об ошибке, о котором вы спрашивали.

Это очень похоже на код:

extern int i;
int *pi = &i;

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

paxdiablo
источник
28
Неверно говорить, что virtual void fn() = 0это определение. Это не определение, а просто декларация . Единственная причина, по которой компоновщик не пытается ее разрешить, заключается в том, что соответствующая запись VMT не будет ссылаться на тело функции (скорее всего, будет содержать нулевой указатель). Однако никто не запрещает вам вызывать эту чисто виртуальную функцию не виртуальным способом, то есть с использованием полностью определенного имени. В этом случае компоновщик будет искать тело, и вам нужно будет определить функцию. И да, вы можете определить тело для чисто виртуальной функции.
2010 года
1
А иногда даже нужно объявить тело для чисто виртуальной функции.
отметьте
3
Компилятор (g ++) скажет вам, что является пропущенным символом. Примечание: в случае динамического связывания библиотеки вы можете получить искаженное имя. Используйте c ++ Filter <mangledNameVariable>, чтобы получить его в удобочитаемой форме. Ошибка typeinfo с именем класса была в моем случае из-за отсутствия реализации виртуального деструктора в некотором базовом классе.
Чмике
1
В вопросе конкретно упоминается, что отсутствует typeinfo, что связано с rtti. Смотрите комментарий Дэймона в stackoverflow.com/questions/11904519/…
wilsonmichaelpatrick
1
@gbmhunter, достаточно справедливо. Сделано изменение.
paxdiablo
150

Это также может произойти, когда вы смешиваете -fno-rttiи -frttiкодируете. Затем необходимо убедиться, что для любого класса, к которому type_infoосуществляется доступ в -frttiкоде, скомпилирован метод key -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;
cdleary
источник
27

Цитирование из руководства gcc :

Для полиморфных классов (классов с виртуальными функциями) объект type_info записывается вместе с vtable [...]. Для всех других типов мы выписываем объект type_info, когда он используется: при применении `typeid 'к выражению, бросая объект или ссылаясь на тип в предложении catch или в спецификации исключений.

И чуть раньше на той же странице:

Если класс объявляет какие-либо не встроенные, не чистые виртуальные функции, первая из них выбирается в качестве «ключевого метода» для класса, а vtable генерируется только в модуле перевода, где определен ключевой метод.

Таким образом, эта ошибка возникает, когда «ключевой метод» не имеет определения, как уже упоминалось в других ответах.

CesarB
источник
2
В моем случае у меня был базовый класс, который объявил, но не определил виртуальные методы, которые не были чисто виртуальными. Как только я сделал их чисто виртуальными, что я и имел в виду, ошибки компоновщика исчезли.
Татьяна Рачева
@TatianaRacheva Спасибо! Сообщение об ошибках от компоновщика менее чем полезно, и для большого интерфейса очень легко пропустить отсутствие '= 0;' для чистого виртуального!
rholmes
21

Если вы связываете один .so с другим, еще одной возможностью является компиляция с "-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
Вам не нужно экспортировать (показывать) базовый класс, если он абстрактный или не используется, только не виртуальные функции, обычно только конструктор. В производных классах с другой стороны , должны быть экспортированы, если они используются.
Крис Хуан-Ливер
похоже на хак, но это действительно помогло решить симптомы с моей стороны. Спасибо !
Малат
16

Предыдущие ответы верны, но эта ошибка также может быть вызвана попыткой использовать typeid для объекта класса, который не имеет виртуальных функций. C ++ RTTI требует vtable, поэтому классы, для которых вы хотите выполнить идентификацию типа, требуют как минимум одну виртуальную функцию.

Если вы хотите, чтобы информация о типах работала с классом, для которого вы на самом деле не хотите никаких виртуальных функций, сделайте деструктор виртуальным.

Тайлер МакГенри
источник
2
Модифицированный, потому что я думаю, что это, скорее всего, является причиной этого конкретного сообщения об ошибке (в отличие от более общего случая неопределенных методов ...)
Alastair
4
Одна вещь, к которой мне пришлось привыкнуть с SO, это не упоминание ответов «выше», поскольку порядок может меняться в зависимости от голосов. Обычно я не ссылаюсь ни на какие другие ответы, поскольку они также могут быть удалены. Я считаю, что ответы должны быть автономными. Я все еще обращаюсь к именам пользователей для атрибуции как бы то ни было.
paxdiablo
Вы можете использовать typeid без vtable; см. мой ответ для цитат из руководства gcc.
CesarB
11

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

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

Важный момент: связывание порядка вопросов с g++. Если вы перечислите библиотеки, которые хотите связать, в неправильном порядке, вы можете получить typeinfoошибку.

Смотрите этот вопрос SO для более подробной информации о связывании порядка с gcc/ g++.

dinkelk
источник
Спасибо!!! Я потратил больше суток, пытаясь выяснить, почему я получаю эту ошибку, и ничего не получалось, пока я не увидел этот ответ и тот, на который вы ссылались. Спасибо!!
Ирэн
10

Возможные решения для кода, которые работают с библиотеками RTTI и не-RTTI:

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

Предположим, что libfoo собран без RTTI. Ваш код использует libfoo и компилируется с RTTI. Если вы используете класс (Foo) в libfoo, который имеет виртуалы, вы, скорее всего, столкнетесь с ошибкой во время компоновки, которая говорит: отсутствует typeinfo для класса Foo.

Определите другой класс (например, FooAdapter), который не имеет виртуальных и будет перенаправлять вызовы в Foo, который вы используете.

Скомпилируйте FooAdapter в небольшой статической библиотеке, которая не использует RTTI и зависит только от символов libfoo. Обеспечьте заголовок для этого и используйте это вместо этого в своем коде (который использует RTTI). Поскольку FooAdapter не имеет виртуальной функции, у него не будет никакой информации о типе, и вы сможете связать свой двоичный файл. Если вы используете много разных классов из libfoo, это решение может быть не удобным, но это только начало.

Francois
источник
Это было для меня, ссылка на библиотеку с различными настройками RTTI.
болотный
6

Подобно обсуждению RTTI, NO-RTTI выше, эта проблема также может возникнуть, если вы используете dynamic_cast и не можете включить объектный код, содержащий реализацию класса.

Я столкнулся с этой проблемой, основываясь на Cygwin, а затем портировал код на Linux. Файлы make, структура каталогов и даже версии gcc (4.8.2) были идентичны в обоих случаях, но код связывался и работал правильно на Cygwin, но не мог связать на Linux. Red Hat Cygwin, по-видимому, сделал модификации компилятора / компоновщика, чтобы избежать требования связывания объектного кода.

Сообщение об ошибке компоновщика Linux правильно направило меня к строке dynamic_cast, но более ранние сообщения на этом форуме заставляли меня искать пропущенные реализации функций, а не реальную проблему: пропущенный объектный код. Мой обходной путь состоял в том, чтобы заменить функцию виртуального типа в базовом и производном классе, например virtual int isSpecialType (), вместо использования dynamic_cast. Этот метод позволяет избежать необходимости связывать код реализации объекта только для того, чтобы заставить dynamic_cast работать должным образом.

FNE
источник
5

В базовом классе (абстрактный базовый класс) вы объявляете виртуальный деструктор и, поскольку вы не можете объявить деструктор как чисто виртуальную функцию, вы также должны определить его прямо здесь, в абстрактном классе, просто фиктивное определение, подобное виртуальному ~ base ( ) {} или в любом производном классе.

Если вы не сделаете этого, вы получите «неопределенный символ» во время ссылки. Поскольку VMT имеет запись для всех чисто виртуальных функций с совпадающим NULL, он обновляет таблицу в зависимости от реализации в производном классе. Но для не чистых, но виртуальных функций, ему нужно определение во время соединения, чтобы можно было обновить таблицу VMT.

Используйте C ++ Filter, чтобы разобрать символ. Например, $ c ++ Filter _ZTIN10storageapi8BaseHostE выведет что-то вроде «typeinfo для storageapi :: BaseHost».

Prashanth
источник
3

Я получил много этих ошибок только сейчас. Случилось так, что я разделил класс только для заголовочного файла на заголовочный файл и файл cpp. Однако я не обновил свою систему сборки, поэтому файл cpp не был скомпилирован. Среди просто неопределенных ссылок на функции, объявленные в заголовке, но не реализованные, я получил много этих ошибок типа info.

Решением было перезапустить систему сборки, чтобы скомпилировать и связать новый файл cpp.

Клаудиу
источник
3

в моем случае я использовал стороннюю библиотеку с заголовочными файлами и другими файлами. я подклассифицировал один класс, и ошибка ссылки, как это произошло, когда я пытаюсь создать экземпляр своего подкласса.

как упомянул @sergiy, зная, что это может быть проблемой 'rtti', мне удалось обойти ее, поместив реализацию конструктора в отдельный файл .cpp и применив к нему флаги компиляции '-fno-rtti' . это работает хорошо.

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

Еще одна вещь, если вы решите попробовать мое решение, попробуйте сделать отдельный файл как можно более простым и не использовать некоторые изящные функции C ++. уделять особое внимание вещам, связанным с повышением, потому что многое зависит от ртти.

uwydoc
источник
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

В моем случае это просто проблема зависимости от библиотеки, даже если у меня есть вызов dynamic_cast. После добавления достаточного количества зависимостей в make-файл эта проблема исчезла.

Чарли
источник
0

Проверьте, что ваши зависимости были скомпилированы без -f-nortti.

Для некоторых проектов вы должны установить это явно, как в RocksDB:

USE_RTTI=1 make shared_lib -j4
Виталий Исаев
источник
0

В моем случае это была виртуальная функция в классе интерфейса, которая не была определена как чисто виртуальная.

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

Я забыл = 0немного.

Мурашки
источник