undefined reference to ‘vtable for std::length_error‘ 问题分析解决
使用 NDK 编译动态库时碰到一个特别奇怪的问题,我们需要调用同事给的静态库然后编译动态库,使用 Android.mk 的方式编译,且一直也没有什么问题,在同事更新了一个库之后编译动态库失败,编译信息如下:
/buildbot/src/android/ndk-release-r17/external/libcxx/include/stdexcept:0: error: undefined reference to 'vtable for std::length_error'
E:/JAVA_ANDROID/android-ndk-r17/build//../toolchains/arm-linux-androideabi-4.9/prebuilt/windows-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin\ld: the vtable symbol may be undefined because the class is missing its key function
/buildbot/src/android/ndk-release-r17/external/libcxx/include/stdexcept:0: error: undefined reference to 'typeinfo for std::length_error'
/buildbot/src/android/ndk-release-r17/external/libcxx/include/stdexcept:0: error: undefined reference to 'std::length_error::~length_error()'
/buildbot/src/android/ndk-release-r17/external/libcxx/src/system_error.cpp:283: error: undefined reference to 'std::runtime_error::~runtime_error()'
/buildbot/src/android/ndk-release-r17/external/libcxx/src/system_error.cpp:283: error: undefined reference to 'std::runtime_error::~runtime_error()'
E:/JAVA_ANDROID/android-ndk-r17/build//../sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(system_error.o):system_error.cpp:vtable for std::__ndk1::system_error: error: undefined reference to 'std::runtime_error::what() const'
E:/JAVA_ANDROID/android-ndk-r17/build//../sources/cxx-stl/llvm-libc++/libs/armeabi-v7a/libc++_static.a(system_error.o):system_error.cpp:typeinfo for std::__ndk1::system_error: error: undefined reference to 'typeinfo for std::runtime_error'
/buildbot/src/android/ndk-release-r17/external/libcxx/src/stdexcept.cpp:0: error: undefined reference to 'vtable for std::logic_error'
出现了一堆标准库链接错误,在 google上 搜了一些 "undefined reference to 'vtable for std::length_error'"
和 "the vtable symbol may be undefined because the class is missing its key function"
的答案,基本上都提到了基类作为抽象类供派生类继承使用,但是由于基类中的析构函数没有定义导致在派生类析构的时候虚函数表中找不到基类的析构函数的定义,从而导致了链接出错的问题。参考如下两个链接:
- https://stackoverflow.com/questions/3065154/undefined-reference-to-vtable
- https://www.cnblogs.com/johnnyflute/p/3673394.html
于是,和同事同步问题之后,一顿 review 代码,但是并没有发现我说的这个问题,于是只能继续找答案。因为看问题报的是标准库 STL 的问题,所以猜测会不会是 Android.mk 文件中的标准库链接出了问题,于是看看 Android.mk 文件中这个变量的值:
LOCAL_LDLIBS
LOCAL_LDFLAGS
一般来说,LOCAL_LDLIBS
用于链接系统库,如 libc
,libc++_static
这样的,这个标志会自动进入系统库目录中去找,且存在同名库的时候会优先选用 动态库;LOCAL_LDFLAGS
则是用于添加第三方库且这个变量有一个特性,就是添加到这里的库需要注意依赖关系,即依赖库在前,被依赖库在后;项目中需要使用到 libc++abi.a
和 libc++_static.a
两个系统静态库,但是这两个库添加到了 LOCAL_LDFLAGS
上了;于是考虑到是否是因为顺序反了导致的链接出错,做如下修改:
# LOCAL_LDFLAGS+= -L$(STL_PATH) -lc++abi -lc++_static
LOCAL_LDFLAGS+= -L$(STL_PATH) -lc++_static -lc++abi
修改之后便顺利编译通过了。这就说明,libc++_Static.a
需要依赖libcabi.a这个库,于是找了 libc++abi.a
这个库的一些资料,发现这个一个网站:
底下有这么一段问答:
Frequently asked questions
Q: Why are the destructors for the standard exception classes defined in libc++abi? They’re just empty, can’t they be defined inline?
A: The destructors for them live in libc++abi because they are “key” functions. The Itanium ABI describes a “key” function as the first virtual declared. And wherever the key function is defined, that is where the type_info gets defined. And in libc++ types are the same type if and only if they have the same type_info (as in there must be only one type info per type in the entire application). And on OS X, libstdc++ and libc++ share these exception types. So to be able to throw in one dylib and catch in another (a std::exception for example), there must be only one std::exception type_info in the entire app. That typeinfo gets laid down beside ~exception() in libc++abi (for both libstdc++ and libc++).
–Howard Hinnant
大意是指 libc++_static
中使用的一些标准库的类的析构函数的定义是在 libc++abi.a
中定义的,即 libc++static.a
需要依赖 libc++abi.a
库,这正好解释了博主将顺序调换一下即可正常编译的做法;
其实,这边应该将这两个库添加到 LOCAL_LDLIBS
上,这样不需要管添加的顺序,且是添加系统库的地方,也比较说得通;总之,这两种方案都可以解决问题。