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

  • Post author:
  • Post category:IT
  • Post comments:0评论

在C++的动态库中,有是为了实现Singleton等功能,经常会使用静态(static)指针变量,并在第一次使用是申请动态分配对象(new); 但其内存的释放往往依赖程序退出时,操作系统来完成内存回收。对于一般的应用,这是没有问题的,但对于C++ 的插件来说,因为其可能在服务程序中被动态的热加载/卸载(dlopen/dlclose),此时,往往会带来内存泄露问题。

下面来看个示例,来说明这种情况:这个例子中,在插件中,声明一个静态成员指针变量 _buff, 并在第1次使用是申请内存10M. 把代码编译成2个功能相同的动态库(插件),然后各 dlopen/dlclose 50次,看看程序的内存使用情况。

  •  DsoBase.h

 

#ifndef __DSO_BASE_H_
#define __DSO_BASE_H_
namespace name_1
{
class DsoBase
{
public:
	DsoBase(){};
    virtual ~DsoBase(){};
    virtual void toString()=0;
};
}
#endif

  • Dso.h
 


#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

 


#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

#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
# -*- 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 时,并不会释放动态库(即插件)内动态申请的内存,所以引起内存泄露。

simon20FE3F05F41933309173AA2C92B0E32A

发表评论