这个问题与this question有点类似,但接受的答案并没有真正提出解决方案或解决方法。
在我们的项目中,我们有一个dylib和主要的可执行文件。 dylib使用-fno-rtti
编译,而可执行文件使用RTTI。当从dylib抛出异常(例如std::bad_alloc
)并在exe中捕获时会发生此问题。
(在你喊“异常需要RTTI以便你必须打开它!”之前,请注意,无论<{1}}或{{1> ,总是生成异常所需的RTTI这实际上记录在-frtti
标志描述中。关于OS X的问题是它不是以相同的方式生成的)
经过一番调查,发现了以下内容:
-fno-rtti
)中,有一个异常RTTI结构的本地副本;特别是-fno-rtti
符号(-fno-rtti
)。__ZTISt9bad_alloc
)从typeinfo for std::bad_alloc
导入typeinfo符号,但没有本地副本由于异常处理代码依赖于比较typeinfo指针来确定异常匹配,因此匹配失败,只有-frtti
成功。
到目前为止,我看到以下选项:
1)使用libstdc++.6.dylib
编译所有内容,或至少编译和捕获异常的文件。这是可行的,但我不喜欢为所有东西生成RTTI的想法,即使我们不使用它;并且使用异常的文件列表很容易过时。
2)当链接dylib时,以某种方式使链接器从目标文件中删除弱异常定义并使用catch(...)
中的那个。到目前为止,我没有成功。
3)???
我做了一个小测试来说明这个问题。
-frtti
运行:
libstdc++.6.dylib
涉及的文件的符号:
--- 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
注意:Linux和Windows上类似的编译选项工作正常。我可以从共享对象/ dll中抛出异常并在主exe中捕获它们,即使它们使用不同的$ ./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++)
选项进行编译。
编辑:以下是我为-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();
}
导入的,因此始终会抛出正确的类型。
答案 0 :(得分:4)
您可以简单地将所有“抛出异常”基础架构移动到启用了-frtti
的帮助程序库中 - 并将其链接到其他内容。如果没有实际代码,很难说这种分解是否可行。
以下是一些示例代码:
// Thrower.cc
void DoThrow() {
throw std::bad_alloc;
}
// LibraryNoRTTI.cc
void f() {
DoThrow();
}
// main.cc
int main() {
try {
f();
}
catch(std::bad_alloc&) {}
return 0;
}
最简单的方法是将所有throw
次调用移至具有相应类型的单独函数,例如:throw std::logical_error("message");
转到void ThrowLogicError(const std::string& message) { ... }
如果封装(私有异常类)存在问题,那么你可以与投掷函数交朋友。
如果您仍想在非rtti库中使用(throw
/ catch
)异常,则必须在库API中使用的内部异常和异常之间进行分离。
好的方法是将内部C ++ throw
- catch
用于内部目的 - 然后根据您的界面将一些使用基于rtti的库函数的异常重新抛出到外部 -
// Thrower.cc
void Rethrow(const std::exception& e) {
throw e;
}
// LibraryNoRTTI.cc
namespace {
void internal_stuff() {
throw std::logical_error("something goes wrong!");
}
} // namespace
// You even may explicitly specify the thrown exceptions in declaration:
void f() throw(std::logical_error) {
try {
internal_stuff();
}
catch(std::exception& e) {
Rethrow(std::logical_error(std::string("Internal error: ") + e.what());
}
}
答案 1 :(得分:2)
开始编辑2014年3月4日
我认为Clang ++编译器有更好的机会获得所需的异常处理。我找到了这个Stack Overflow帖子:Clang and the default compiler in OS X Lion。该帖子提供了有用的脚本行,用于修改~/.bashrc
以更改Snow Leopard上的系统默认编译器设置以及如何使用LLVM GCC。对于Clang,请在~/.bashrc
:
# Set Clang as the default compiler for the system
export CC=clang
export CFLAGS=-Qunused-arguments
export CPPFLAGS=-Qunused-arguments
如果c++
符号链接不存在,可以直接调用clang ++或根据需要添加c ++链接(例如
ln -s /usr/bin/clang++ c++
)。最好通过运行:
来检查/usr/bin
中的所有符号链接
ls -l `which lynx` | more
在我的Mavericks命令行工具安装c++
指向clang++
和cc
指向clang
。 g++
编译器版本说:
$ g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx- include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.0.0
Thread model: posix
clang++
编译器版本说:
$clang++ --version
Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.0.0
Thread model: posix
请注意,g++
包含目录路径设置为/usr/include/c++/4.2.1
,可能不是解决问题所需的包含路径。
MacPorts:希望是任何OS X版本的答案
我可以找到任何OS X版本的任何Clang ++编译器版本的最佳解决方案是使用名为MacPorts的开源工具。 MacPorts Guide中有大量文档。该应用程序名为port
,可以从OS X安装包安装,也可以获取源代码并在本地编译。以下是从安装MacPorts到Snow Leopard。其他OS X版本应该类似。获取Snow Leopard的MacPorts后,运行port search命令以观察所有可用的clang相关端口。例如,它看起来像这样:
$port search clang
来自Snow Leopard 10.6.8的部分搜索结果列表是:
clang-2.9 @2.9_13 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.0 @3.0_12 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.1 @3.1_7 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.2 @3.2_2 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.3 @3.3_2 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.4 @3.4 (lang)
C, C++, Objective C and Objective C++ compiler
clang-3.5 @3.5-r202097 (lang)
C, C++, Objective C and Objective C++ compiler
clang_select @0.1 (sysutils)
common files for selecting default clang version
然后我成功安装了clang-3.3和sudo port install clang-3.3
。完成此操作后,请键入port select --list clang
以查看可用版本。然后运行
sudo port select --set clang mp-clang-3.3
或类似的。当我执行clang++ --version
时,它(如预期的那样):
clang version 3.3 (tags/RELEASE_33/final)
Target: x86_64-apple-darwin10.8.0
Thread model: posix
与执行clang --version
命令时相同(关闭并重新启动终端后):
clang version 3.3 (tags/RELEASE_33/final)
Target: x86_64-apple-darwin10.8.0
Thread model: posix
许多OS X版本都有MacPorts安装包(例如Leopard,Snow Leopard,Lion,Mountain Lion,Mavericks等)。我的搜索没有比Leopard更进一步。如果使用的是比Leopard更早的OS X,请仔细阅读MacPorts网站。
如果对于在哪里找到Xcode 4.2(或曾经能够获得它)的细节感到好奇,我发现这篇关于获取Snow Leopard Xcode 4.2 download for Snow Leopard的Xcode 4.2的帖子。然后是另外两个:Can i use the latest features of C++11 in XCode 4 or OSX Lion? [duplicate]和Can I use C++11 with Xcode?。尝试了几个链接,看看4.2 Xcode是否仍然适用于Snow Leopard,没有任何乐趣。
为了获得完整的C ++ 11支持,很可能需要安装MacPorts libc ++。要安装更新版本,请执行sudo port install libcxx
。 /usr/lib
的内容将被当前的C ++ 11库覆盖(根据需要,按照MacPorts Ticket#42385:libcxx/libcxxabi: OS update can render system unusable
如果仍然缺少libc ++,请尝试:"libc++" C++ Standard Library。然后使用:
$ export TRIPLE=-apple-
$ export MACOSX_DEPLOYMENT_TARGET=10.6
$ ./buildit
来自How to build libc++ with LLVM/Clang 3.3 on Mac OS X 10.6 "Snow Leopard"。
在OS X Lion,Mountain Lion和Mavericks上,他们最近都在Apple Developer网站上下载了独立的命令行工具。 Clang版本可能比您需要的版本旧,因此在使用Developer站点命令行工具'Clang时,请务必确认需要哪些C ++ 11功能。
结束编辑2014年3月4日
上述宏检测可能需要从__GNUC__
更改为__clang__
或__clang_version__
。这完全取决于每个OS X编译器的预定义编译器宏是什么,以及这里根据需要检测的最佳方法。在What predefined macro can I use to detect clang?处的堆栈溢出答案应该有助于配置命令行以获取它们(例如clang++ -dM -E -x c /dev/null
)。
我注意到在运行前面的示例命令时,有一个名为__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
的预定义宏。在小牛clang++
上,宏值为1090
。可能需要有一个#ifdef
逻辑系列来为每个OS X clang ++编译器设置适当的EXPORT
宏。
答案 2 :(得分:1)
好吧,即使我已经接受了答案,但并没有解决所有问题。所以我写下了最终有效的解决方案。
我制作了一个小工具,用于对目标文件进行后期处理,并将本地符号标记为UNDEF
。这会强制链接器使用libstdc++
中的定义,而不是文件中的本地定义。该工具的基本方法是:
LC_SYMTAB
命令struct nlist
)和字符串__ZTISt9bad_alloc
)N_UNDF|N_EXT
。(我也为ELF做了类似的实现)
我对任何使用std异常的文件进行后处理,无论是用于抛出还是用于捕获。为确保文件列表不会过时,我使用nm
为不需要的本地符号添加了链接后检查。
这似乎解决了我迄今为止遇到的所有问题。