C++【如何避免内存泄露】

前言

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++的内存管理有很多文章介绍,包括语法、模式设计、检查工具等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值