欢迎访问 Lu程序设计
C/C++使用Lu键树实现智能指针及检查内存泄露
1 说明
要演示本文的例子,你必须下载Lu64脚本系统。本文的例子需要lu64.dll、lu64.lib、C++格式的头文件lu64.h,相信你会找到并正确使用这几个文件。
用C/C++编译器创建一个控制台应用程序,复制本文的例子代码直接编译运行即可。
2 关于智能指针及Lu实现
由于 C/C++ 语言没有自动内存回收机制,程序员每次 malloc/new 出来的内存都要手动 free/delete。由于程序流程太复杂,程序员容易忘记 free/delete,造成内存泄露。C++用智能指针可以有效缓解这类问题, 例如:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr等等,可谓种类繁多。
实际上,C/C++程序使用Lu脚本的键树系统也可以解决此类问题,而且程序员可以方便地查找哪块内存忘记了free/delete。阅读完本文,你就可以尝试使用Lu脚本系统查找你的C/C++程序是否存在内存泄露了。
关于Lu脚本键树系统,可以参考Lu编程指南,同时参考这个教程:C/C++使用Lu脚本字符串键树。
3 代码
代码1:实现智能指针
#include <windows.h> #include <iostream> #include "lu64.h" #pragma comment( lib, "lu64.lib" ) using namespace std; #define key_Simple (luPriKey_User-20) //将对该私有键加锁 #define imax 5 //私有键值最大数 void _stdcall Del_Simple(void *me); //销毁Simple的函数 class Simple //编写一个简单的类帮助测试 { public: int number; //数据 bool bDelete; //为帮助测试新增的数据,对象初始化时bDelete=false,对象销毁时bDelete=true Simple(int param=0) { //在类的初始化函数中增加以下几行代码 void *me,*NowKey; //NowKey返回已存在的键值 me=this; InsertKey((char *)&(me),sizeof(luVOID),key_Simple,this,Del_Simple,NULL,1,NowKey); //在Lu键树中注册键值 bDelete=false; number = param; cout << "Simple: " << number << endl; }; ~Simple() { //在类的初始化函数中增加以下几行代码 void *me; if(!bDelete) //如果用户手动delete { bDelete=true; me=this; DeleteKey((char *)&(me),sizeof(luVOID),key_Simple,Del_Simple,0); //在Lu键树中删除键值 } cout << "~Simple: " << number << endl; }; }; void _stdcall Del_Simple(void *me) //销毁Simple的函数,将注册到Lu系统,以实现自动销毁Simple { Simple *pSimple; pSimple=(Simple*)me; if(pSimple->bDelete==false) //如果用户忘记了delete { pSimple->bDelete=true; delete pSimple; //这是Lu系统自动delete } } LuData _stdcall OpLock_Simple(luINT m,LuData *Para,void *hFor,int theOperator) //Simple的运算符重载函数 { LuData a; a.BType=luStaData_nil; a.VType=luStaData_nil; a.x=0; //不对Simple类型的对象做运算,总返回nil return a; } void main(void) { Simple *pSimple[imax]; int i; if(!InitLu()) return; //初始化Lu LockKey(key_Simple,Del_Simple,OpLock_Simple); //在Lu键树中加锁键,只能存储Simple类型 for(i=0;i<imax;i++) { pSimple[i]=new Simple(i); } cout << endl << "--- C++ delete 删除约1/2对象 ---" << endl << endl; for(i=0;i<imax/2;i++) { delete pSimple[i]; } cout << endl << "--- Lu系统解锁键并销毁全部对象 ---" << endl << endl; LockKey(key_Simple,NULL,OpLock_Simple); //在Lu键树中解锁键 FreeLu(); //释放Lu }
运行结果:
Simple: 0
Simple: 1
Simple: 2
Simple: 3
Simple: 4
--- C++ delete 删除约1/2对象 ---
~Simple: 0
~Simple: 1
--- Lu系统解锁键并销毁全部对象 ---
~Simple: 2
~Simple: 4
~Simple: 3
代码2:查找未释放对象(内存)
#include <windows.h> #include <iostream> #include "lu64.h" #pragma comment( lib, "lu64.lib" ) using namespace std; #define key_Simple (luPriKey_User-20) //将对该私有键加锁 #define imax 5 //私有键值最大数 void _stdcall Del_Simple(void *me); //销毁Simple的函数 class Simple //编写一个简单的类帮助测试 { public: int number; //数据 bool bDelete; //为帮助测试新增的数据,对象初始化时bDelete=false,对象销毁时bDelete=true Simple(int param=0) { //在类的初始化函数中增加以下几行代码 void *me,*NowKey; //NowKey返回已存在的键值 me=this; InsertKey((char *)&(me),sizeof(luVOID),key_Simple,this,Del_Simple,NULL,1,NowKey); //在Lu键树中注册键值 bDelete=false; number = param; cout << "Simple: " << number << endl; }; ~Simple() { //在类的初始化函数中增加以下几行代码 void *me; if(!bDelete) //如果用户手动delete { bDelete=true; me=this; DeleteKey((char *)&(me),sizeof(luVOID),key_Simple,Del_Simple,0); //在Lu键树中删除键值 } cout << "~Simple: " << number << endl; }; }; void _stdcall Del_Simple(void *me) //销毁Simple的函数,将注册到Lu系统,以实现自动销毁Simple { Simple *pSimple; pSimple=(Simple*)me; if(pSimple->bDelete==false) //如果用户忘记了delete { pSimple->bDelete=true; delete pSimple; //这是Lu系统自动delete } } LuData _stdcall OpLock_Simple(luINT m,LuData *Para,void *hFor,int theOperator) //Simple的运算符重载函数 { LuData a; a.BType=luStaData_nil; a.VType=luStaData_nil; a.x=0; //不对Simple类型的对象做运算,总返回nil return a; } int _stdcall GetKeySimpleValue(char *KeyStr,int ByteNum,void *KeyValue) //枚举Lu系统中指定键值类型的所有对象 { Simple *pSimple; pSimple=(Simple *)KeyValue; cout << "Simple->number: " << pSimple->number << endl; return 1; } void main(void) { Simple *pSimple[imax]; int i; char KeyStr[sizeof(luVOID)+1]; //枚举未销毁的对象用,长度比指针长度大1 if(!InitLu()) return; //初始化Lu LockKey(key_Simple,Del_Simple,OpLock_Simple); //在Lu键树中加锁键,只能存储Simple类型 for(i=0;i<imax;i++) { pSimple[i]=new Simple(i); } cout << endl << "--- C++ delete 删除约1/2对象 ---" << endl << endl; for(i=0;i<imax/2;i++) { delete pSimple[i]; } cout << endl << "--- 使用Lu枚举未释放的所有对象 ---" << endl << endl; EnumKey(key_Simple,KeyStr,sizeof(luVOID)+1,GetKeySimpleValue); cout << endl << "--- Lu系统解锁键并销毁全部对象 ---" << endl << endl; LockKey(key_Simple,NULL,OpLock_Simple); //在Lu键树中解锁键 FreeLu(); //释放Lu }
运行结果:
Simple: 0
Simple: 1
Simple: 2
Simple: 3
Simple: 4
--- C++ delete 删除约1/2对象 ---
~Simple: 0
~Simple: 1
--- 使用Lu枚举未释放的所有对象 ---
Simple->number: 2
Simple->number: 4
Simple->number: 3
--- Lu系统解锁键并销毁全部对象 ---
~Simple: 2
~Simple: 4
~Simple: 3
代码3:效率测试之一,未使用Lu缓冲区
#include <windows.h> #include <iostream> #include <time.h> #include "lu64.h" #pragma comment( lib, "lu64.lib" ) using namespace std; #define key_Simple (luPriKey_User-20) //将对该私有键加锁 #define imax 10 //私有键值最大数,修改此数据时要同时修改下面的循环次数kmax int kmax = 100000; //循环次数 void _stdcall Del_Simple(void *me); //销毁Simple的函数 class Simple //编写一个简单的类帮助测试:使用Lu系统 { public: int number; //数据 bool bDelete; //为帮助测试新增的数据,对象初始化时bDelete=false,对象销毁时bDelete=true Simple(int param=0) { //在类的初始化函数中增加以下几行代码 void *me,*NowKey; //NowKey返回已存在的键值 me=this; InsertKey((char *)&(me),sizeof(luVOID),key_Simple,this,Del_Simple,NULL,1,NowKey); //在Lu键树中注册键值 bDelete=false; //初始化代码 number = param; }; ~Simple() { //在类的初始化函数中增加以下几行代码 void *me; if(!bDelete) //如果用户手动delete { bDelete=true; me=this; DeleteKey((char *)&(me),sizeof(luVOID),key_Simple,Del_Simple,0); //在Lu键树中删除键值 } //销毁对象的其他代码 }; }; void _stdcall Del_Simple(void *me) //销毁Simple的函数,将注册到Lu系统,以实现自动销毁Simple { Simple *pSimple; pSimple=(Simple*)me; if(pSimple->bDelete==false) //如果用户忘记了delete { pSimple->bDelete=true; delete pSimple; //这是Lu系统自动delete } } LuData _stdcall OpLock_Simple(luINT m,LuData *Para,void *hFor,int theOperator) //Simple的运算符重载函数 { LuData a; a.BType=luStaData_nil; a.VType=luStaData_nil; a.x=0; //不对Simple类型的对象做运算,总返回nil return a; } class SimpleCpp //编写一个简单的类帮助测试:不使用Lu系统 { public: int number; //数据 SimpleCpp(int param=0) { //初始化代码 number = param; }; ~SimpleCpp() { //销毁对象的其他代码 }; }; void main(void) { Simple *pSimple[imax]; SimpleCpp *pSimpleCpp[imax]; int i,j; clock_t start, end; start = clock(); for(j=0;j<kmax;j++) { for(i=0;i<imax;i++) { pSimpleCpp[i]=new SimpleCpp(i); } for(i=0;i<imax;i++) { delete pSimpleCpp[i]; } } end = clock(); cout << "--- C/C++ 系统运行时间 : " << end - start << " ms." << endl; //////////////////////////////////////////////////////////////////////////// if(!InitLu()) return; //初始化Lu LockKey(key_Simple,Del_Simple,OpLock_Simple); //在Lu键树中加锁键,只能存储Simple类型 start = clock(); for(j=0;j<kmax;j++) { for(i=0;i<imax;i++) { pSimple[i]=new Simple(i); } for(i=0;i<imax;i++) { delete pSimple[i]; } } end = clock(); cout << "--- C/C++ Lu 复合系统运行时间 : " << end - start << " ms." << endl; LockKey(key_Simple,NULL,OpLock_Simple); //在Lu键树中解锁键 FreeLu(); //释放Lu }
运行结果:
--- C/C++ 系统运行时间 : 249 ms.
--- C/C++ Lu 复合系统运行时间 : 1489 ms.
代码4:效率测试之二,使用Lu缓冲区
#include <windows.h> #include <iostream> #include <time.h> #include "lu64.h" #pragma comment( lib, "lu64.lib" ) using namespace std; #define key_Simple (luPriKey_User-20) //将对该私有键加锁 #define imax 10 //私有键值最大数,修改此数据时要同时修改下面的循环次数kmax int kmax = 100000; //循环次数 void _stdcall Del_Simple(void *me); //销毁Simple的函数 class Simple //编写一个简单的类帮助测试:使用Lu系统 { public: int number; //数据 bool bDelete; //为帮助测试新增的数据,对象初始化时bDelete=false,对象销毁时bDelete=true Simple(int param=0) { //在类的初始化函数中增加以下几行代码 void *me,*NowKey; //NowKey返回已存在的键值 me=this; InsertKey((char *)&(me),sizeof(luVOID),key_Simple,this,Del_Simple,NULL,1,NowKey); //在Lu键树中注册键值 bDelete=false; //初始化代码 number = param; }; ~Simple() { //在类的初始化函数中增加以下几行代码 void *me; if(!bDelete) //如果用户手动delete { bDelete=true; me=this; DeleteKey((char *)&(me),sizeof(luVOID),key_Simple,Del_Simple,0); //在Lu键树中删除键值 } //销毁对象的其他代码 }; }; Simple *NewSimple(int param=0) //设计专用的生成Simple的函数 { Simple *pSimple; char keyname[sizeof(luVOID)]; pSimple=(Simple *)GetBufObj(key_Simple,keyname); //先尝试从缓冲区中获取一个Simple对象 if(pSimple) //获得对象后初始化 { pSimple->number=param; } else //生成新的对象并初始化 { pSimple=new Simple(param); } return pSimple; } void _stdcall Del_Simple(void *me) //销毁Simple的函数,将注册到Lu系统,以实现自动销毁Simple { Simple *pSimple; pSimple=(Simple*)me; if(pSimple->bDelete==false) //如果用户忘记了delete { pSimple->bDelete=true; delete pSimple; //这是Lu系统自动delete } } LuData _stdcall OpLock_Simple(luINT m,LuData *Para,void *hFor,int theOperator) //Simple的运算符重载函数 { LuData a; a.BType=luStaData_nil; a.VType=luStaData_nil; a.x=0; //不对Simple类型的对象做运算,总返回nil return a; } class SimpleCpp //编写一个简单的类帮助测试:不使用Lu系统 { public: int number; //数据 SimpleCpp(int param=0) { //初始化代码 number = param; }; ~SimpleCpp() { //销毁对象的其他代码 }; }; void main(void) { Simple *pSimple[imax]; SimpleCpp *pSimpleCpp[imax]; int i,j; clock_t start, end; start = clock(); for(j=0;j<kmax;j++) { for(i=0;i<imax;i++) { pSimpleCpp[i]=new SimpleCpp(i); } for(i=0;i<imax;i++) { delete pSimpleCpp[i]; } } end = clock(); cout << "--- C/C++ 系统运行时间 : " << end - start << " ms." << endl; //////////////////////////////////////////////////////////////////////////// if(!InitLu()) return; //初始化Lu LockKey(key_Simple,Del_Simple,OpLock_Simple); //在Lu键树中加锁键,只能存储Simple类型 start = clock(); for(j=0;j<kmax;j++) { for(i=0;i<imax;i++) { pSimple[i]=NewSimple(i); //使用专用的生成Simple的函数 } for(i=0;i<imax;i++) { //delete pSimple[i]; //通过在Lu键树中删除键值的方法销毁对象,参数1表示先将对象放入缓冲池,由Lu系统择机销毁 DeleteKey((char *)&(pSimple[i]),sizeof(luVOID),key_Simple,Del_Simple,1); } } end = clock(); cout << "--- C/C++ Lu 复合系统运行时间 : " << end - start << " ms." << endl; LockKey(key_Simple,NULL,OpLock_Simple); //在Lu键树中解锁键 FreeLu(); //释放Lu }
运行结果:
--- C/C++ 系统运行时间 : 254 ms.
--- C/C++ Lu 复合系统运行时间 : 79 ms.
4 函数说明
本例用到了Lu的7个输出函数:初始化Lu的函数InitLu,释放Lu的函数FreeLu, 插入键值的函数InsertKey,删除键值的函数DeleteKey,加锁键函数LockKey,枚举键值的函数EnumKey,从缓冲池中获取一个对象的函数GetBufObj。从这里查看这些函数的说明:Lu编程指南。
5 难点分析
代码1实现了智能指针。 对C/C++程序来说,仅需要在类的构造函数和析构函数中添加几行代码;当然,加载Lu核心库、对指定的数据类型进行加锁和解锁还是必不可少的。
代码2在代码1的基础上实现了对C/C++程序内存泄露的检查。仅增加了一个枚举键值的函数EnumKey。
代码3对使用Lu智能指针的对象生成及销毁效率进行了测试,并与C/C++做了比较,结果表明:在不使用Lu缓冲池时,C/C++和Lu复合程序的效率约为C/C++程序的1/6左右。
代码4仍然对使用Lu智能指针的对象生成及销毁效率进行测试,但使用Lu缓冲池,结果表明:在使用Lu缓冲池时,C/C++和Lu复合程序的效率接近C/C++程序的3倍。 使用Lu缓冲池需要付出的代价是,你必须专门设计一个生成对象的函数来代替new操作符,销毁对象时也不要直接delete,而要使用Lu系统的函数DeleteKey。另外,代码4在小范围内进行了对象的生成及销毁的测试(对象数目 imax=10),充分利用了Lu缓冲池,这是符合一般程序运行情况的;如果修改代码4中的 imax=1000 及 kmax=1000(循环次数)再进行测试,将会发现,C/C++和Lu复合程序的效率将退化为代码3的情况。
6 其他
你可能注意到了,我的联系方式就在下面,如有不明之处或有什么建议,可随时与我进行联系。
版权所有© Lu程序设计
2002-2021,保留所有权利
E-mail: forcal@sina.com
QQ:630715621
最近更新:
2021年05月23日