编程小贴士

给你的编程提供小点子


C++插件中使用静态指针变量引起的内存泄露问题

在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;
}

 

  • Scons 的 SContruct
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# -*- mode: python -*-
import sys, os, os.path, platform, re, time
env = Environment()
env.AppendUnique(CCFLAGS = '-g')
env.AppendUnique(CCFLAGS = '-m64')
env.AppendUnique(CCFLAGS = '-DTARGET_64')
env.AppendUnique(LINKFLAGS = '-m64')
dso_sources = ['Dso.cpp' ]
env.SharedLibrary('dso_a', dso_sources)
env.SharedLibrary('dso_b', dso_sources)
env.Program('test_main',['test_main.cpp'], LIBS=['dl'], LIBPATH='.',LINKFLAGS = '-export-dynamic' )

 

  • 编译

[static_var_and_dynamic_lib_t2]$scons -c && scons

scons: Reading SConscript files …

scons: done reading SConscript files.

scons: Cleaning targets …

Removed Dso.os

Removed libdso_a.so

Removed libdso_b.so

Removed test_main.o

Removed test_main

scons: done cleaning targets.

scons: Reading SConscript files …

scons: done reading SConscript files.

scons: Building targets …

g++ -o Dso.os -c -g -m64 -DTARGET_64 -fPIC Dso.cpp

g++ -o libdso_a.so -m64 -shared Dso.os

g++ -o libdso_b.so -m64 -shared Dso.os

g++ -o test_main.o -c -g -m64 -DTARGET_64 test_main.cpp

g++ -o test_main -export-dynamic test_main.o -L. -ldl

scons: done building targets

  • 执行测试程序

[static_var_and_dynamic_lib_t2]$./test_main

Dso constructor

_buff=0x2b65d7f31010

Dso constructor

_buff=0x2b65d8b34010

Dso destructor

Dso destructor

Dso constructor

_buff=0x2b65d9535010

Dso constructor

_buff=0x2b65da138010

Dso destructor

Dso destructor

Dso constructor

_buff=0x2b65dab39010

Dso constructor

  •  内存使用情况—mem_user

从程序开始执行到结束, 系统 mem_user 从 1.7G (图中1M 表示内存1G )涨到近 2.7G,左右,增涨1G, 正好是2个插件(libdso_a.so 和 libdso_b.so),50次dlopen/dlclose,每次申请 10M 的内存累积量。
可见 dlclose 时,并不会释放动态库(即插件)内动态申请的内存,所以引起内存泄露。

tips1000_2014-08-03_08-48-37

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>