我使用NDK独立工具链(版本8)来编译应用程序和库.
Android版本是2.2.1 Froyo.
以下是简单共享库的源代码.
#include <stdio.h> int iii = 0; int *ptr = NULL; __attribute__((constructor)) static void init() { iii = 653; } __attribute__((destructor)) static void cleanup() { } int aaa(int i) { printf("aaa %d\n",iii); }
这是使用上述库的程序源代码.
#include <dlfcn.h> #include <stdlib.h> #include <stdio.h> int main() { void *handle; typedef int (*func)(int); func bbb; printf("start...\n"); handle = dlopen("/data/testt/test.so",RTLD_LAZY); if (!handle) { return 0; } bbb = (func)dlsym(handle,"aaa"); if (bbb == NULL) { return 0; } bbb(1); dlclose(handle); printf("exit...\n"); return 0; }
有了这些资源,一切都运行正常,但是当我尝试使用一些STL函数或类时,当main()函数退出时,程序会崩溃,例如当使用该源代码进行共享库时.
#include <iostream> using namespace std; int iii = 0; int *ptr = NULL; __attribute__((constructor)) static void init() { iii = 653; } __attribute__((destructor)) static void cleanup() { } int aaa(int i) { cout << iii << endl; }
使用此代码,程序会在分段故障后或在main()函数退出时崩溃.
我已经尝试过几次测试,并发现以下结果.
>没有使用STL,一切都正常.
>当使用STL并且最后不要调用dlclose()时,一切都正常.
>我尝试使用-fno-use-cxa-atexit或-fuse-cxa-atexit等各种编译标记进行编译,结果是一样的.
在使用STL的代码中有什么问题?
这是简单类的源代码:
myclass.h
class MyClass { public: MyClass(); ~MyClass(); void Set(); void Show(); private: int *pArray; };
myclass.cpp
#include <stdio.h> #include <stdlib.h> #include "myclass.h" MyClass::MyClass() { pArray = (int *)malloc(sizeof(int) * 5); } MyClass::~MyClass() { free(pArray); pArray = NULL; } void MyClass::Set() { if (pArray != NULL) { pArray[0] = 0; pArray[1] = 1; pArray[2] = 2; pArray[3] = 3; pArray[4] = 4; } } void MyClass::Show() { if (pArray != NULL) { for (int i = 0; i < 5; i++) { printf("pArray[%d] = %d\n",i,pArray[i]); } } }
从代码中可以看出,我没有使用任何与STL相关的东西.
这是函数库导出的源文件.
func.h
#ifdef __cplusplus extern "C" { #endif int SetBabe(int); int ShowBabe(int); #ifdef __cplusplus } #endif
func.cpp
#include <stdio.h> #include "myclass.h" #include "func.h" MyClass cls; __attribute__((constructor)) static void init() { } __attribute__((destructor)) static void cleanup() { } int SetBabe(int i) { cls.Set(); return i; } int ShowBabe(int i) { cls.Show(); return i; }
最后这是使用库的程序的源代码.
main.cpp中
#include <dlfcn.h> #include <stdlib.h> #include <stdio.h> #include "../simple_lib/func.h" int main() { void *handle; typedef int (*func)(int); func bbb; printf("start...\n"); handle = dlopen("/data/testt/test.so",RTLD_LAZY); if (!handle) { printf("%s\n",dlerror()); return 0; } bbb = (func)dlsym(handle,"SetBabe"); if (bbb == NULL) { printf("%s\n",dlerror()); return 0; } bbb(1); bbb = (func)dlsym(handle,"ShowBabe"); if (bbb == NULL) { printf("%s\n",dlerror()); return 0; } bbb(1); dlclose(handle); printf("exit...\n"); return 0; }
再次,你可以看到程序使用库也没有使用任何STL相关的东西,但在程序运行后,我得到相同的分段错误在主(…)功能退出.所以这个问题与STL本身没有联系,它隐藏在别的地方.经过长时间的研究,我发现了这个bug.通常,静态C变量的析构函数会在main(…)函数退出之前立即被调用,如果它们在main程序中定义,或者如果它们在某个库中定义并且正在使用它,那么析构函数应该立即被调用dlclose(…).在Android操作系统上,在main(…)函数退出时调用静态C变量的所有析构函数(在主程序中定义或在某些库中使用).那么在我们的情况下会发生什么呢?我们在我们使用的库中定义了cls static C变量.然后在main(…)函数退出之前,我们调用dlclose(…)函数,结果库关闭,cls变为无效.但是cls的指针存储在某个地方,在main(…)函数退出时应该调用析构函数,因为在调用时它已经无效了,我们得到了分段错误.所以解决方案是不要调用dlclose(…),一切都应该是好的.不幸的是,使用这个解决方案,我们不能使用attribute((destructor))来对要重新初始化的东西进行初始化,因为它被调用为dlclose(…)调用的结果.