Вопрос несколько похож на на этот вопрос, но принятый ответ действительно не предлагает решение или обходное решение.
В нашем проекте у нас есть dylib и основной исполнитель. Dylib скомпилирован с -fno-rtti
, в то время как исполняемый файл использует RTTI. Проблема возникает, когда исключение (например, std::bad_alloc
) выбрасывается из dylib и попадает в exe.
(Прежде чем вы будете кричать "исключениям нужно RTTI, чтобы вы его получили!", обратите внимание, что RTTI, необходимый для исключений, всегда создается независимо от настроек -frtti
или -fno-rtti
. Это фактически документируется в -fno-rtti
. Проблема в OS X заключается в том, что она не генерируется таким же образом)
После некоторого исследования было обнаружено следующее:
- В dylib (
-fno-rtti
) имеется локальная копия структур RTTI исключения; в частности, символ__ZTISt9bad_alloc
(typeinfo for std::bad_alloc
). - exe (
-frtti
) импортирует символ типаinfo изlibstdc++.6.dylib
и не имеет локальной копии
Поскольку код обработки исключений основан на сравнении указателей типаinfo для определения соответствия исключения, соответствие не выполняется, и только catch(...)
преуспевает.
Пока я вижу следующие варианты:
1) скомпилируйте все или, по крайней мере, файлы, которые бросают и захватывают исключения, с помощью -frtti
. Это выполнимо, но мне не нравится идея генерации RTTI для всего, даже если мы его не используем; и список файлов, которые работают с исключениями, подвержен усталости.
2) при связывании dylib каким-то образом заставить компоновщика выбрасывать слабые исключения из объектного файла и использовать один из libstdc++.6.dylib
. Пока что мне не удалось.
3)
Я сделал небольшой тест, иллюстрирующий проблему.
--- throw.cpp ---
#include <iostream>
#if defined(__GNUC__)
#define EXPORT __attribute__((visibility("default")))
#else
#define EXPORT __declspec(dllexport)
#endif
EXPORT void dothrow ()
{
std::cout << "before throw" << std::endl;
throw std::bad_alloc();
}
--- main.cpp ---
#include <stdio.h>
#include <iostream>
#if defined(__GNUC__)
#define IMPORT extern
#else
#define IMPORT __declspec(dllimport)
#endif
IMPORT void dothrow ();
int main (void) {
try {
std::cout << "trying lib->main exception" << std::endl;
dothrow ();
}
catch ( const std::bad_alloc& )
{
std::cout << "caught bad_alloc in main - good." << std::endl;
}
catch (...)
{
std::cout << "caught ... in main - bad!" << std::endl;
}
}
--- makefile ---
# for main exe
CFLAGS_RTTI=-m32 -frtti -fvisibility=hidden -fvisibility-inlines-hidden -shared-libgcc -funwind-tables
# for dylib
CFLAGS_NORTTI=-m32 -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -shared-libgcc
# for linking; some switches which don't help
CLFLAGS=-Wl,-why_live,-warn_commons,-weak_reference_mismatches,error,-commons,error
all: test
test: libThrow.dylib main.o
g++ $(CFLAGS_RTTI) -o test main.o -lthrow -L./ $(CLFLAGS)
main.o: main.cpp
g++ $(CFLAGS_RTTI) -c -o main.o main.cpp
throw.o: throw.cpp
g++ $(CFLAGS_NORTTI) -c -o throw.o throw.cpp
libThrow.dylib: throw.o
g++ $(CFLAGS_NORTTI) -dynamiclib -o libThrow.dylib throw.o
clean:
rm test main.o throw.o
Продолжительность:
$ ./test
trying lib->main exception
before throw
caught ... in main - bad!
Символы используемых файлов:
$ nm -m throw.o | grep bad_alloc
000001be (__TEXT,__textcoal_nt) weak private external __ZNSt9bad_allocC1Ev
000001be (__TEXT,__textcoal_nt) weak private external __ZNSt9bad_allocC1Ev
00000300 (__TEXT,__eh_frame) weak private external __ZNSt9bad_allocC1Ev.eh
(undefined) external __ZNSt9bad_allocD1Ev
00000290 (__DATA,__const_coal) weak external __ZTISt9bad_alloc
000002a4 (__TEXT,__const_coal) weak external __ZTSSt9bad_alloc
(undefined) external __ZTVSt9bad_alloc
$ nm -m libThrow.dylib | grep bad_alloc
00000ce6 (__TEXT,__text) non-external __ZNSt9bad_allocC1Ev
(undefined) external __ZNSt9bad_allocD1Ev (from libstdc++)
00001050 (__DATA,__const) weak external __ZTISt9bad_alloc
00000e05 (__TEXT,__const) weak external __ZTSSt9bad_alloc
(undefined) external __ZTVSt9bad_alloc (from libstdc++)
$ nm -m main.o | grep bad_alloc
(undefined) external __ZTISt9bad_alloc
$ nm -m test | grep bad_alloc
(undefined) external __ZTISt9bad_alloc (from libstdc++)
Примечание. Аналогичные варианты компиляции в Linux и Windows прекрасно работают. Я могу исключать исключения из общего объекта /dll и ловить их в главном exe, даже если они скомпилированы с различными параметрами -frtti
/-fno-rtti
.
РЕДАКТИРОВАТЬ: вот как я решил это решить для конкретного случая bad_alloc
:
#if defined(__GLIBCXX__) || defined(_LIBCPP_VERSION)
#define throw_nomem std::__throw_bad_alloc
#else
#define throw_nomem throw std::bad_alloc
#endif
EXPORT void dothrow ()
{
std::cout << "before throw" << std::endl;
throw_nomem();
}
Функция __throw_bad_alloc
импортируется из libstdc++.6.dylib
и поэтому всегда выдает правильный тип.