前言
C++语言以它编程高度灵活性著称,特别是指针。俗话说“指针在手,天下我有”。
可是任何事情都不是完美的,有利则有弊。C++把指针交给使用者,则使用者必须时刻注意内存泄露、野指针造成的崩溃等问题。
本文在多年的编程经验基础上对如何避免内存泄露进行了总结,希望对大家有所帮助。
共享堆
对于一些需要分配比较大块的内存,并且大小基本固定的应用,建议软件启动时在创建一块固定大小的“共享堆”;在软件退出时,释放它。并且中间创建对象的内存尽量在该“共享堆”上分配。这样的好处可以减少“堆”的创建和释放,提供性能,特别对大块的堆,是非常影响性能的,并且也会容易出现“内存碎片”。
简单共享内存的示例代码如下:
class complex
{
public:
complex(double r =0 , double i= 0) : re(r), im(i) {}
~complex(void) {}
protected:
friend inline complex operator+(const complex& x, const complex& y) {
return complex(x.re+y.re, x.im+y.im);
}
public:
double re, im;
};
// 计算
void calc_complex(unsigned char* pSubShareMem)
{
// 共享堆上创建对象A
complex* a = new(pSubShareMem) complex(1,2);
// 移动共享堆指针到空闲区
pSubShareMem += sizeof(complex);
// 共享堆上创建对象B
complex* b = new(pSubShareMem) complex(3,4);
// 移动共享堆指针到空闲区
pSubShareMem += sizeof(complex);
// 共享堆上创建对象C
complex* c = new(pSubShareMem) complex();
// 使用对象A、B、C
*c = *a + *b;
// 打印结果
cout << c->re << "; " << c->im << endl;
}
int main() {
// 创建共享堆
unsigned char* pShareMem = new unsigned char[10*1024*1024];
// 在共享堆上具体应用
for (int i=0; i<10; i++)
{
calc_complex(pShareMem);
}
// 释放共享堆
delete[] pShareMem;
// 暂停
system("pause");
return 0;
}
说明:
- 在多线程的应用中,可以把共享堆分块管理,这样确保多线程并行处理,有兴趣的读者可以自己实现。
- 运行中总体的内存消耗不能确定的应用,可以拆解软件的各个模块的内存消耗,进行局部的共享堆的应用。
智能指针
C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
C++98仅仅提供了auto_ptr,在C++11版本之后提供,包含在头文件中,shared_ptr、unique_ptr、weak_ptr 。而auto_ptr不支持数组下标操作,C++11已经建议不再使用它了。
支持数组操作的智能指针及限制说明(参考:c/c++ 数组的智能指针 使用):
- unique_ptr的数组智能指针,没有*和->操作,但支持下标操作[]
- shared_ptr的数组智能指针,有*和->操作,但不支持下标操作[],只能通过get()去访问数组的元素
- shared_ptr的数组智能指针,必须要自定义deleter
智能指针对数组操作的代码示例如下:
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class test{
public:
explicit test(int d = 0) : data(d){cout << "new" << data << endl;}
~test(){cout << "del" << data << endl;}
void fun(){cout << data << endl;}
public:
int data;
};
int main(){
//test* t = new test[2];
unique_ptr<test[]> up(new test[2]);
up[0].data = 1;
up[0].fun();
up[1].fun();
shared_ptr<test> sp(new test[2], [](test* p){delete [] p;});
(sp.get())->data = 2;//数组的第一个元素
sp->data = 10;
test& st = *sp;
st.data = 20;
(sp.get() + 1)->data = 3;//数组的第二个元素
return 0;
}
函数间智能指针的引用,代码示例如下:
// 创建智能指针
unique_ptr<unsigned char[]> create_unique_ptr()
{
unique_ptr<unsigned char[]> aptr(new unsigned char[10*1024*1024]);
return aptr;
}
// 子函数使用智能指针
unique_ptr<unsigned char[]> call_unique_ptr()
{
unique_ptr<unsigned char[]> pch = create_unique_ptr();
pch[0] = 'a';
return pch;
}
// 主函数
int main() {
// 主函数使用智能指针
for (int i=0; i<100; i++)
{
unique_ptr<unsigned char[]> pch = call_unique_ptr();
pch[1] = 'b';
}
// 暂停
system("pause");
return 0;
}
智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。
下面是一个简单智能指针的demo(参考:C++11中智能指针的原理、使用、实现)。
#include <iostream>
#include <memory>
template<typename T>
class SmartPointer {
private:
T* _ptr;
size_t* _count;
public:
SmartPointer(T* ptr = nullptr) :
_ptr(ptr) {
if (_ptr) {
_count = new size_t(1);
} else {
_count = new size_t(0);
}
}
SmartPointer(const SmartPointer& ptr) {
if (this != &ptr) {
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
}
}
SmartPointer& operator=(const SmartPointer& ptr) {
if (this->_ptr == ptr._ptr) {
return *this;
}
if (this->_ptr) {
(*this->_count)--;
if (this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
return *this;
}
T& operator*() {
assert(this->_ptr == nullptr);
return *(this->_ptr);
}
T* operator->() {
assert(this->_ptr == nullptr);
return this->_ptr;
}
~SmartPointer() {
(*this->_count)--;
if (*this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}
size_t use_count(){
return *this->_count;
}
};
int main() {
{
SmartPointer<int> sp(new int(10));
SmartPointer<int> sp2(sp);
SmartPointer<int> sp3(new int(20));
sp2 = sp3;
std::cout << sp.use_count() << std::endl;
std::cout << sp3.use_count() << std::endl;
}
//delete operator
}
结束语
C++的内存管理有很多文章介绍,包括语法、模式设计、检查工具等。