c++动态内存管理

c语言中我们学习了malloc,calloc,realloc,free这四个函数,前面都是c语言用来动态申请内存的函数(他们都是在堆上申请内存)最后一个函数是释放前面三个所申请的空间。
返回值:成功的话返回所申请空间的地址,失败返回NULL。
下面我们看看malloc,calloc,realloc他们的不同
1. malloc:函数原型为void *malloc( size_t size );size为所要申请的字节数大小;它所申请的内存的内容没有被初始化,是一部分垃圾数值。所以通常我们用void *memset( void *dest, int c, size_t count );初始化申请的空间。
2. calloc:函数原型为void *calloc( size_t num, size_t size );num为申请的所需类型的个数,size为每个类型所需要的字节数;它不同于malloc的是:他所申请的空间都被初始化为0;
3. realloc:它是一个扩容函数(增容或减容),函数原型为void *realloc( void *memblock, size_t size );memblock为原来所申请空间的大小,size为扩容后的字节大小。如果memblock为NULL,他就和malloc函数一样。这个函数分为三种情况考虑:第一种,进行减容,那么最后得到的所申请空间的地址将和memblock的地址相同;第二种,这个函数在申请空间时会多申请一部分空间,当我们需要的增容的空间 小于多申请的空空间,那么它将在原来申请的空间后面继续申请 所要增加的空间。最后返回的地址也是原来空间的地址;第三种,我们要增容的大小大于多申请的预留的空间,那么他将从开开辟一段空间,将原来空间的内容复制到新的空间,然后释放原理的空间,最后返回新的空间地址
常见的内存泄露问题
1.没有释放所申请的空间(最普遍,最容易犯的)
2.没有正确释放或者部分释放所申请的空间。
3.在类的构造函数和析构函数中没有匹配的调用new和delete函数.
4.在释放对象数组时在delete中没有使用方括号。
5.指向对象的指针数组不等同于对象数组。
6.缺少拷贝构造函数。
7.缺少重载赋值运算符。
8.没有将基类的析构函数定义为虚函数。
如何检测内存泄露
1.对象计数法
方法:在对象构造时++,析构时–。
优点:没有性能开销,定位准确。
缺点:需修改现有代码,而且对于第三方库、STL容器、脚本泄漏等因无法修改代码而无法定位。
2.重载new和delete
方法:重载new/delete,记录分配点(甚至是调用堆栈),定期打印。
缺点:侵入式方法,需将头文件加入到大量源文件的头部,以确保重载的宏能够覆盖所有的new/delete。记录分配点需要加锁(如果你的程序是多线程),而且记录分配要占用大量内存(也是占用的程序内存)。
3.Hook Windows系统AP
方法: 使用微软的detours库,hook分配内存的系统Api:HeapAlloc/HeapRealloc/HeapFree(new/malloc的底层调用),记录分配点,定期打印。
缺点:记录内存需要占用大量内存,而且多线程环境需要加锁。


malloc/free和 new/delete的区别
1.他们都是动态的申请空间,并且都在栈上申请。
2.malloc/free既可以用于c语言也可以用于c++。而new/delete只能用于c++.
3.malloc需要指出所要申请空间字节数的大小,而new只需要知道所要申请空间的类型即可。
4.在c++中,new/delete会调用类的构造和析构函数,而malloc/free则不调用。
5.他们要成对使用,否则会出现内存泄露。
6.malloc/free是c/c++的标准库函数,而new/delete是c++的操作符。
new/new[]/delete/delete[]的执行原理图
new/delete原理图
这里写图片描述
new[]/delete[]原理图
这里写图片描述
operator new 和定位new表达式

void * operator new (size_t size);
void operator delete (size_t size);
void * operator new [](size_t size);
void operator delete[] (size_t size);

定位new表达式:在已经开辟成功的空间执行构造函数

Test* p1=(Test*)malloc(sizeof(Test));
new (p1) Test;

一般的静态的创建对象,将在栈上;而动态的创建对象,将在堆上创建对象。
一个类如何只在堆上创建对象
当对象建立在栈上面时,是由编译器分配内存空间的,调用构造函数来构造栈对象。当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。编译器管理了对象的整个生命周期。如果编译器无法调用类的析构函数,情况会是怎样的呢?比如,类的析构函数是私有的,编译器无法调用析构函数来释放内存。所以,编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存。
方法:将类的析构函数设置为私有属性

class B
{
public:
    B()
    {

    }
private:
    ~B()
    {

    }
};

一个类如何只在栈上创建对象
只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。将operator new()设为私有即可

class  D  
{  
private :  
    void * operator  new ( size_t  t){}      // 注意函数的第一个参数和返回值都是固定的   
    void  operator  delete ( void * ptr){}  // 重载了new就需要重载delete   
public :  
    D(){}  
    ~D(){}  
}; 
  • 4
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值