<六>内存分配&智能指针内存泄漏问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;

class Point{
public:
  Point(int x, int y, int z):x(x),y(y),z(z){ 
    cout<<"ctor"<<endl;
  }
  ~Point(){ cout<<"dtor"<<endl;}

  virtual void print(){
    cout<<x<<" "<<y<<" "<<z<<endl;
  }

  int x;
  int y;
  int z;
};

int main()
{  
   cout<<sizeof(Point)<<endl;
   void* memory = std::malloc(sizeof(Point));

   long *p=(long *)memory;
   std::cout<<*p<<std::endl;//第一个位置放的是虚函数表指针

   p++;
   int* p1=(int*)p;
   for(int i=0;i<3;i++)
   {
      cout<<*p1<<" ";
      p1++;
   }

   //在指定内存位置构建对象Point
   Point* myObject = ::new (memory) Point[{100,200,300}];
   myObject->print();

  long* p=(long*)memory;
  cout<<*p<<endl;
  p++;

  int* pi=reinterpret_cast<int*>(p);
  cout<<*pi++<<endl;
  cout<<*pi++<<endl;
  cout<<*pi<<endl;
  
   myObject->~Point();
   std::free(memory);

 
  // int data=100;
  // double dvalue=234.323;
  //int value =(int)dvalue C风格转换,不推荐了
  // static_cast:相关类型转型可能造成内存位的丢失
  // int value=static_cast<int>(dvalue); 
  // static_cast  子类指针可以直接赋值给父类指针是兼容的,但是
  // 也可以使用static_cast 来强调子类转型到了父类 

  // //dynamic_cast: 多态转型 , 基类必须有虚函数(虚析构函数)
  // Sub s1;
  // Base& base=s1;
  // Sub& s=dynamic_cast<Sub&> (base);

  // //reinterpret_cast: 二进制一致性转型
  // //const_cast: 去常量性
  

    //标准库针对placement_new 做了更好用的封装如下
    void* memory = std::malloc(3*sizeof(Point));
    Point* myObject = reinterpret_cast<Point*>(memory);
    std::uninitialized_fill_n(myObject, 3, Point{100,200,300});
    //3表示写几次,3次那么就要预先申请3倍的内存空间
 
    std::destroy_at(myObject);
    std::free(memory);

    //void* 转到其他类型的指针也可以使用static_cast 也可以 reinterpret_cast
    //建议统一使用reinterpret_cast

}

//placement_new 常使用的场景,大量小对象的频繁开辟销毁,造成
//内存碎片

关于operator new operator delete重载
在这里插入图片描述

类内部operator_new operator_delete

#include <iostream>
using namespace std;

//void* T::operator new  ( std::size_t count );
//void* T::operator new[]( std::size_t count );

class MyClass{
public:
  MyClass(const string& s):_s(s){}
  MyClass(){ cout<<"ctor"<<endl;}
  ~MyClass(){ cout<<"dtor"<<endl;}
  void process(){
    cout<<_s<<endl;
  }

  void* operator new(size_t size)   {
    cout<<"class new "<<endl;

   data++;
   //data+=size;
    return ::operator new(size); 调用全局new
  } 
  void operator delete(void* p)  {
    cout<<"class delete "<<endl;
    ::operator delete(p); 

    data--;
  } 

  static int data;
private:
  string _s;
};

int MyClass::data=0;

int main()
{
  
  MyClass* myObject = new MyClass{"Software"};  
  myObject->process();             
  delete myObject;

  cout<<"-----------"<<endl;
  MyClass* p = ::new MyClass{}; //绕过自定义类内部的operator new 使用全局的 operator new
  ::delete p;
 

}

全局operator_new operator_delete

#include <iostream>
#include <vector>
using namespace std;

int alloc_times = 0; 
int dealloc_times = 0; 
int allocated = 0; 


class MyClass{
 
private:
  double x,y,z;
};


void* operator new(size_t size)  { 
  void* p = std::malloc(size); 
  //std::cout << "allocated " << size << " byte(s)\n"; 

  allocated+=size;
  alloc_times++;
  return p; 
} 
 
void operator delete(void* p) noexcept  { 
  //std::cout << "deleted memory\n"; 

  dealloc_times++;
  return std::free(p); 
}

void* operator new[](size_t size)   {
  void* p = std::malloc(size); 
  allocated+=size;
  alloc_times++;

  std::cout << "allocated " << size << " byte(s) with new[]\n"; 
  return p; 
} 
void operator delete[](void* p) noexcept  { 
  std::cout << "deleted memory with delete[]\n"; 
   dealloc_times++;
  return std::free(p); 
}

int main()
{
  
  {
    MyClass* v=new MyClass[10];//24*10=240

    cout<<allocated<<endl;

    for(int i=0;i<10;i++)
    {
      MyClass* pc=new MyClass();//10
      delete pc;
    }
     delete[] v;

  }

  //MyClass* p = ::new MyClass{}; 
  //::delete p;

  cout<<allocated<<endl;
  cout<<alloc_times<<endl;
  cout<<dealloc_times<<endl;

}

全局operator_new operator_delete 结合share_ptr weak_ptr一些有意思的代码 1.1

#include <iostream>
#include <memory>
using namespace std;

class MyClass{

public:
    MyClass(){ cout<<"MyClass()构造函数"<<endl;}
    ~MyClass(){ cout<<"~MyClass()析构函数"<<endl;}
    
private:
  double x,y,z;

};


int alloc_times = 0; 
int dealloc_times = 0; 
int allocated = 0; 


void* operator new(size_t size)  { 
  void* p = std::malloc(size); 
  cout << "operator new " << size << " byte(s)"<<endl; 

  allocated+=size;
  alloc_times++;
  return p; 
} 
 
void operator delete(void* p) noexcept  { 
  cout << "operator deleted memory"<<endl; 
  dealloc_times++;
  return std::free(p); 
}

int main(){

    //weak_ptr<MyClass> w;
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
    {
         shared_ptr<MyClass> s{new MyClass()};
        //shared_ptr<MyClass> s = std::make_shared<MyClass>();
        //w=s; 
       
   }   
    //1. s析构、释放原始对象、但不释放控制块
    cout<<"block end--------"<<endl;
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
  
    //w.reset(); //2. w析构、释放控制块
    //cout<<"分配尺寸:"<<allocated<<endl;
    //cout<<"分配次数"<<alloc_times<<endl;
    //cout<<"释放次数"<<dealloc_times<<endl;
      
}

//运行结果如下
/*
分配尺寸:0
分配次数0
释放次数0
operator new 24 byte(s)
MyClass()构造函数
operator new 24 byte(s)
~MyClass()析构函数
operator deleted memory
operator deleted memory
block end--------
分配尺寸:48
分配次数2
释放次数2

//我们来对上面运行结果进行分析
智能指针创建方式: shared_ptr<MyClass> s{new MyClass()};
运行结果中第一个  operator new 24 byte(s) 这24个字节是创建 new MyClass()的时候发生空间开辟动作, 然后紧接着调用MyClass的构造函数

第二个 operator new 24 byte(s) 是给谁的??
我们再上一篇内容 <三>智能指针之__sharePtr 第一张图揭示了 sharePtr的内存结构
sharePtr 中有两条指针, 第一条指向MyClass()对象,这个对象中有三个double成员,所以消费了24个字节空间
第二条指针指向了控制块,那控制块对象有哪些成员呢?控制块对象又需要多少个字节存储空间呢?首先运行结果是24个字节空间
我们下面通过跟踪源码 看一下  控制块的的内部结构,如下图 , 看完下图就知道这24个字节其中一个是指向原对象的指针,
另外两个是long 类型的计数器


当 shared_ptr<MyClass> s{new MyClass()}; 出{}作用域后 析构了两次,一个是MyClass的,还有一个是控制块的

接下来我们将上面代码中的//都去掉
主要关注点
  1:  //weak_ptr<MyClass> w;
  2:  //w=s;
  3:
    //w.reset(); w.reset()开和不开 两种不同结果,看看不开的情况 //2. w析构、释放控制块
    //cout<<"分配尺寸:"<<allocated<<endl;
    //cout<<"分配次数"<<alloc_times<<endl;
    //cout<<"释放次数"<<dealloc_times<<endl;

 见下面代码 1.2

*/

在这里插入图片描述

全局operator_new operator_delete 结合share_ptr weak_ptr一些有意思的代码 1.2

#include <iostream>
#include <memory>
using namespace std;

class MyClass{

public:
    MyClass(){ cout<<"MyClass()构造函数"<<endl;}
    ~MyClass(){ cout<<"~MyClass()析构函数"<<endl;}
    
private:
  double x,y,z;

};


int alloc_times = 0; 
int dealloc_times = 0; 
int allocated = 0; 


void* operator new(size_t size)  { 
  void* p = std::malloc(size); 
  cout << "operator new " << size << " byte(s)"<<endl; 

  allocated+=size;
  alloc_times++;
  return p; 
} 
 
void operator delete(void* p) noexcept  { 
  cout << "operator deleted memory"<<endl; 
  dealloc_times++;
  return std::free(p); 
}

int main(){

    weak_ptr<MyClass> w;
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
    {
         shared_ptr<MyClass> s{new MyClass()};
        //shared_ptr<MyClass> s = std::make_shared<MyClass>();
        w=s; 
       
   }   
    //1. s析构、释放原始对象、但不释放控制块
    cout<<"block end--------"<<endl;
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
  
    //w.reset(); //2. w析构、释放控制块
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
      
}

//运行结果如下
/*

分配尺寸:0
分配次数0
释放次数0
operator new 24 byte(s)
MyClass()构造函数
operator new 24 byte(s)
~MyClass()析构函数
operator deleted memory
block end--------
分配尺寸:48
分配次数2
释放次数1
分配尺寸:48
分配次数2
释放次数1
operator deleted memory

//我们来对上面运行结果进行分析
智能指针创建方式: shared_ptr<MyClass> s{new MyClass()};
运行结果中第一个  operator new 24 byte(s) 这24个字节是创建 new MyClass()的时候发生空间开辟动作, 然后紧接着调用MyClass的构造函数

第二个 operator new 24 byte(s) 是给控制块的,这个我们再代码1.1已经学习到了
我们再没有打开  //w.reset(); 这个代码的情况下,居然只析operator_delete了一次,这一次就是给MyClass的内存释放用的
为什么控制块的堆内存没能得到正常的operter_delete? 这个不是内存泄漏吗?确实属于内存泄漏,原因在于 
  weak_ptr<MyClass> w;
  ..
  ..
  w=s; 
  我们用一个弱引用挂住了share_ptr
  w=s; 会使得弱引用计数 +1  见下面第一张图

  我们现在通过跟踪源码看看为什么operator_delete一次,见下面第二张图

  通过看完下面第二张图,我知道了出来share_ptr作用域 对象应用计数==0所以对象delete
  但是还有一个w=s挂着弱引用,使得弱引用计数不为0,到时弱引用计数指针指向的堆无法被释放
  即内存泄漏!!
*/

 接下来我们看代码1.3  我们激活 w.reset(); //2. w析构、释放控制块

第一张图
在这里插入图片描述
第二张图
在这里插入图片描述
全局operator_new operator_delete 结合share_ptr weak_ptr一些有意思的代码 1.3

#include <iostream>
#include <memory>
using namespace std;

class MyClass{

public:
    MyClass(){ cout<<"MyClass()构造函数"<<endl;}
    ~MyClass(){ cout<<"~MyClass()析构函数"<<endl;}
    
private:
  double x,y,z;

};


int alloc_times = 0; 
int dealloc_times = 0; 
int allocated = 0; 


void* operator new(size_t size)  { 
  void* p = std::malloc(size); 
  cout << "operator new " << size << " byte(s)"<<endl; 

  allocated+=size;
  alloc_times++;
  return p; 
} 
 
void operator delete(void* p) noexcept  { 
  cout << "operator deleted memory"<<endl; 
  dealloc_times++;
  return std::free(p); 
}

int main(){

    weak_ptr<MyClass> w;
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
    {
         shared_ptr<MyClass> s{new MyClass()};
        //shared_ptr<MyClass> s = std::make_shared<MyClass>();
        w=s; 
       
   }   
    //1. s析构、释放原始对象、但不释放控制块
    cout<<"block end--------"<<endl;
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
  
    w.reset(); //2. w析构、释放控制块
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
      
}

//运行结果如下

分配尺寸:0
分配次数0
释放次数0
operator new 24 byte(s)
MyClass()构造函数
operator new 16 byte(s)
~MyClass()析构函数
operator deleted memory
block end--------
分配尺寸:40
分配次数2
释放次数1
operator deleted memory
分配尺寸:40
分配次数2
释放次数2


 在w.reset()之前释放次数为1,在w.reset()之后dealloc_times又加了1,这样释放次数就为2,
 所以在w.reset()之前,原对象已经正常释放了,在w.reset()之后控制块又正常释放了


 ------------------------
 接下来 我们 将代码中的
 shared_ptr<MyClass> s{new MyClass()};
 换成 shared_ptr<MyClass> s = std::make_shared<MyClass>(); 看看 见代码1.4


全局operator_new operator_delete 结合share_ptr weak_ptr一些有意思的代码 1.4


#include <iostream>
#include <memory>
using namespace std;

class MyClass{

public:
    MyClass(){ cout<<"MyClass()构造函数"<<endl;}
    ~MyClass(){ cout<<"~MyClass()析构函数"<<endl;}
    
private:
  double x,y,z;

};


int alloc_times = 0; 
int dealloc_times = 0; 
int allocated = 0; 


void* operator new(size_t size)  { 
  void* p = std::malloc(size); 
  cout << "operator new " << size << " byte(s)"<<endl; 

  allocated+=size;
  alloc_times++;
  return p; 
} 
 
void operator delete(void* p) noexcept  { 
  cout << "operator deleted memory"<<endl; 
  dealloc_times++;
  return std::free(p); 
}

int main(){

    weak_ptr<MyClass> w;
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
    {
         //shared_ptr<MyClass> s{new MyClass()};
        shared_ptr<MyClass> s = std::make_shared<MyClass>();
        w=s; 
       
   }   
    //1. s析构、释放原始对象、但不释放控制块
    cout<<"block end--------"<<endl;
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
  
    w.reset(); //2. w析构、释放控制块
    cout<<"分配尺寸:"<<allocated<<endl;
    cout<<"分配次数"<<alloc_times<<endl;
    cout<<"释放次数"<<dealloc_times<<endl;
      
}

-----运行结果
分配尺寸:0
分配次数0
释放次数0
operator new 40 byte(s)
MyClass()构造函数
~MyClass()析构函数
block end--------
分配尺寸:40
分配次数1
释放次数0
operator deleted memory
分配尺寸:40
分配次数1
释放次数1

//上面的结果我有以下几个点需要关注
1:operator new()只发生了一次,即内存只开辟了一次(相比shared_ptr<MyClass> s{new MyClass()};这种方式)
2:在w.reset();之前 哪怕shared_ptr<MyClass> s = std::make_shared<MyClass>(); 出了作用域, 基础对象
 都没有operator_delete, 与shared_ptr<MyClass> s{new MyClass()} 不一样,

3:w.reset()之后 operator_delete发生了一次

针对上面的关注点,我们来分析一下
1:shared_ptr<MyClass> s = std::make_shared<MyClass>(); 与shared_ptr<MyClass> s{new MyClass()};
智能指针的构建过程和方式不一样
a:shared_ptr<MyClass> s{new MyClass()}new MyClass() 第二步再在堆上创建控制块对象,所以operator_new 了两次
b:shared_ptr<MyClass> s = std::make_shared<MyClass>(); 直接将MyClass对象所需内存大小+控制块内存所需大小 一次分配了,
所以只调用了一次operator_new ,大小40字节,(这种一次开辟的内存控制是无法区分MyClass和控制块分别delete的,只能一次性回收所有40个字节)

2:正是由于上面b:中描述的原因这一块大内存区域还有weak_ptr还在占用,导致share_ptr出了作用域也无法释放空间,但是w.reset()之后由于没有弱引用再使用内存了,那么整块内存就可以回收了

3:所有如果没有w.rest() 那么这个40个字节一直无法释放,相当于内存泄漏了.


哪些情况场景下,我们会用到内存分配自定义?

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值