我使用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(…)调用的结果.