在C++的动态库中,有是为了实现Singleton等功能,经常会使用静态(static)指针变量,并在第一次使用是申请动态分配对象(new); 但其内存的释放往往依赖程序退出时,操作系统来完成内存回收。对于一般的应用,这是没有问题的,但对于C++ 的插件来说,因为其可能在服务程序中被动态的热加载/卸载(dlopen/dlclose),此时,往往会带来内存泄露问题。
下面来看个示例,来说明这种情况:这个例子中,在插件中,声明一个静态成员指针变量 _buff, 并在第1次使用是申请内存10M. 把代码编译成2个功能相同的动态库(插件),然后各 dlopen/dlclose 50次,看看程序的内存使用情况
- DsoBase.h
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#ifndef __DSO_BASE_H_
#define __DSO_BASE_H_
namespace
name_1
{
class
DsoBase
{
public
:
DsoBase(){};
virtual
~DsoBase(){};
virtual
void
toString()=0;
};
}
#endif
|
- Dso.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#ifndef __DSO_DSO_H_
#define __DSO_DSO_H_
#include <stdint.h>
#include <string>
#include "DsoBase.h"
namespace
name_1
{
class
Dso:
public
DsoBase
{
public
:
Dso() ;
virtual
~Dso() ;
virtual
void
toString();
private
:
static
pthread_mutex_t _mutex;
static
char
* _buff;
// 静态成员变量
const
uint32_t MAX_LEN;
};
}
#endif
|
- Dso.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#include <stdio.h>
#include "Dso.h"
namespace
name_1
{
pthread_mutex_t Dso::_mutex = PTHREAD_MUTEX_INITIALIZER;
char
* Dso::_buff=NULL;
// 静态成员变量
Dso::Dso():MAX_LEN(10*1024*1024)
{
printf
(
"Dso constructor\n"
);
}
Dso::~Dso()
{
printf
(
"Dso destructor\n"
);
}
void
Dso::toString()
{
pthread_mutex_lock(&_mutex);
if
(_buff == NULL)
{
//第1次使用是申请分配10M内存
_buff=(
char
*)
malloc
(MAX_LEN);
memset
(_buff,0x0,MAX_LEN);
printf
(
"_buff=%p\n"
,_buff);
}
pthread_mutex_unlock(&_mutex);
}
}
extern
"C"
name_1::DsoBase * CreateFun()
{
return
new
name_1::Dso();
}
extern
"C"
void
DestroyFun(name_1::DsoBase * obj)
{
delete
obj;
}
|
- test_main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
#include <stdio.h>
#include <dlfcn.h>
#include "pthread.h"
#include "Dso.h"
typedef
name_1::DsoBase * (*CreateFunT)();
typedef
void
(*DestroyFunT)(name_1::DsoBase * p);
int
main(
int
argc ,
char
** argv)
{
// 模拟 ha3 插件更新过程
for
(
int
i=0; i <50; i ++ )
{
//1. dl_open libdso_a.so
void
* dl_a=dlopen(
"./libdso_a.so"
,RTLD_NOW);
if
(!dl_a ) {
printf
(
"dlopen return %s"
, dlerror());
return
-1;
}
CreateFunT createFun_a= (CreateFunT)dlsym(dl_a ,
"CreateFun"
);
DestroyFunT destoryFunc_a = (DestroyFunT)dlsym(dl_a ,
"DestroyFun"
);
name_1::DsoBase* obj_a=createFun_a();
obj_a->toString();
//2. dl_open libdso_b.so
void
* dl_b=dlopen(
"./libdso_b.so"
,RTLD_NOW);
if
(!dl_b ) {
printf
(
"dlopen return %s"
, dlerror());
return
-1;
}
CreateFunT createFun_b= (CreateFunT)dlsym(dl_b ,
"CreateFun"
);
DestroyFunT destoryFunc_b = (DestroyFunT)dlsym(dl_b ,
"DestroyFun"
);
name_1::DsoBase* obj_b=createFun_b();
obj_b->toString();
sleep(5);
//3. dl_close libdso_a.so
destoryFunc_a(obj_a);
dlclose(dl_a);
sleep(5);
//4. 使用 obj_b
obj_b->toString();
//5. dl_close libdso_a.so
destoryFunc_b(obj_b);
dlclose(dl_b);
sleep(20);
}
return
0;
}
|
从程序开始执行到结束, 系统 mem_user 从 1.7G (图中1M 表示内存1G )涨到近 2.7G,左右,增涨1G, 正好是2个插件(libdso_a.so 和 libdso_b.so),50次dlopen/dlclose,每次申请 10M 的内存累积量。
可见 dlclose 时,并不会释放动态库(即插件)内动态申请的内存,所以引起内存泄露。