c++—内存管理、智能指针、内存池
1. 内存分析诊断工具:valgrind;
2. 内存管理的两种方式:
①用户管理:自己申请的,自己用,自己回收;效率高,但容易导致内存泄漏;
②系统管理:系统自动回收垃圾;安全性高,但消耗内存资源;
3. 内存分配的两种方式:
(1)静态分配(数组),存在于栈空间;
①优点:物理地址连续,方便遍历;
②缺点:分配与利用效率较低;
(2)动态分配(malloc、new),存在于堆空间;
①优点:使用效率较高;
②缺点:物理地址不连续;
4. 防止内存泄漏的方法:
①养成良好的编码规范;
②使用指针时,初始化就置空,避免野指针;
③malloc后及时free;new后及时delete;
④利用内存检测工具,如valgrind进行辅助处理;且可以利用g++选项工具同步分析;
5. 为什么c++没有GC机制?
①没有共同的基类:c++从C语言演变而来,可以直接操作指针,且类型之间可以相互转换,对于一个指针无法直到它实际指向类型;
②系统开销比较大:垃圾回收带来的系统开销,需要占用更多的内存,违反了c++的设计哲学“不为不必要的功能支付代价”,不符合高效特性;
③且c++中有析构函数实现智能指针,通过引用计数来管理资源的释放,对GC的需求不迫切;
6. 智能指针
(1)智能指针的作用:帮助开发者对动态分配的对象进行声明周期管理,可以有效地防止内存泄漏;
(2)分类,可以分为三类
①unique_ptr:独占指针
②shared_ptr:共享指针
③weak_ptr:弱指针
(3)shared_ptr:共享指针
①共享:多个指针可以同时指向同一个对象,当最后一个指针被销毁或者指向其他对象时,这个对象会被释放;
②工作原理:内部有计数器,实时记录该共同空间被几个智能指针指向;其本质是对裸指针进行分装,下有封装例子;
③导致引用计数增加的情况:用一个智能指针初始化另一个智能指针、函数传参(传递一个智能指针,即复制),函数返回值(返回一个智能指针);
④引用计数减少的情况:给智能指针赋予新值指向一个新的对象、局部的智能指针离开作用域;
⑤语法:(注意:初始化不可以用 “ = ” 号),有两种初始化的方法(手动初始化、函数初始化std::make_shared函数)
shared_ptr<int>pi(new int(5)); //(初始化1)内部调用的构造函数
shared_ptr<int>pi = new int(5); //错误用法!
auto p = std::make_shared<string>("hello world"); //(初始化2)函数方法
int *p = pi.get(); //获得pi的裸指针,注意智能指针一旦释放,裸指针也就失效;
pi.reset(); //将pi置空;
pi.use_count(); //返回该智能指针的引用计数;
pi.reset(new int (6)); //改变pi的指向;
delete p; //释放;
delete p; //释放数组指针;void test(shared_ptr<int>&p) //智能指针作为形参
{}reinterpret_pointer_cast<>(); //可以将任意类型的指针进行转换;
⑥自定义删除器,一些情况下,默认删除器处理不了(shared_ptr管理动态数组),需要自己指定删除器,下有详细例子;
⑧指针类型转换函数
static_pointer_cast //void*与裸指针转换
dynamic_pointer_cast //向下类型转型(基类→派生类),基类需要有虚函数;
const_pointer_cast //去除裸指针的const属性;
reinterpret_pointer_cast //任意类型之间裸指针转换;
(4)unique_ptr:独占指针
①同一时刻,只能有一个unique_ptr指向这个对象,当指针销毁,指向的对象也销毁;
②语法,(注意:初始化不可以用 “ = ” 号),有两种初始化的方法(手动初始化、函数初始化std::make_unique函数)
unique_ptr<int>p(new int(5));
unique_ptr<int>p = new int(5); //错误用法:不可以使用=
atuo pi = std::make_unique<string>("hello world");
③其他操作方法例如reset()、get()等于shared_ptr一样;
(5)weak_ptr:独占指针
①weak_ptr是弱指针,不是独立的指针,不能单独操作所指向的资源;
②用处一:weak_ptr辅助shared_ptr的使用(监视shared_ptr 指向对象的生命周期);
③用处二:weak_ptr和shared_ptr之间可以相互转换,shared_ptr可以赋给weak_ptr,反过来不可以;
(6)自己封装的智能指针shared_ptr<>();
hpp:
#pragma once
#include <iostream>
using namespace std;
//自己封装的智能指针shared_ptr<>();
template <typename T>
class Smartpointer
{
public:template <typename U>friend class Smartpointer; //声明一种友元类Smartpointer() : m_p(nullptr){//m_p = nullptr;m_count = new long(0);}explicit Smartpointer(T * p) //禁止隐式转换,就是不可以使用=,只能使用()初始化{cout<<"explicit"<<endl;if(p != nullptr){m_p = p;m_count = new long(1); //次数计为1}else{m_p = nullptr;m_count = new long(0); //次数为0}}template <typename U>Smartpointer(T *p, Smartpointer<U> &other) //另外一种类型U?{if(p != nullptr){m_p = other.m_p;m_count = other.m_count;(*m_count)++;}else{m_p = nullptr;m_count = other.m_count;}}Smartpointer(Smartpointer<T> &other) //拷贝构造{cout<<"拷贝构造"<<endl;if(other.m_p != nullptr){m_p = other.m_p;m_count = other.m_count;(*m_count)++;}else{m_p = nullptr;m_count = other.m_count;}}Smartpointer<T> & operator=(const Smartpointer<T> &other) //=重载{cout<<"operator="<<endl;if(other.m_p != nullptr){m_p = other.m_p;m_count = other.m_count;(*m_count)++;}else{m_p = other.m_p;m_count = other.m_count;}return *this;}Smartpointer(Smartpointer<T>&&other) //移动构造{cout<<"移动构造"<<endl;if(other.m_p = nullptr){m_p = other.m_p;other.m_p = nullptr;m_count = other.m_count;other.m_count = nullptr;}else{m_p = nullptr;m_count = other.m_count;}}~Smartpointer() //析构函数{if((m_p != nullptr) && (--(*m_count) == 0)){cout<<"~Smartpointer"<<endl;delete m_p;delete m_count;}else{m_p = nullptr;m_count = nullptr;}}long use_count() const //函数后面加const的作用?{return *m_count;}T * get() //返回裸指针{return m_p;}void reset(T *p){if(p = nullptr){m_p = p;(*m_count)--;m_count = new long(1); //引用计数从新置为1}else{m_p = nullptr;m_count = new long(0);}}void reset(){m_p = nullptr;(*m_count)--;}void swap(Smartpointer<T> &other){std::swap(m_p,other.m_p); //c库内置函数实现交换std::swap(m_count,other.m_count);}T operator*(){ cout<<"operator*"<<endl;return *m_p;}T * operator->(){return m_p;}operator bool(){return m_p != nullptr;}T operator[](int index) //重载[]{return m_p[index];}template <typename T1, typename U1>friend Smartpointer<T1> static_pointer_cast(Smartpointer<U1>&other); //声明一种友元函数,用于不同类型的智能指针转换private:T * m_p; //裸指针long * m_count; //引用计数指针
};template <typename T, typename U>
Smartpointer<T> static_pointer_cast(Smartpointer<U>&other)
{T * temp = static_cast<T *>(other.m_p);return Smartpointer(temp,other);
}
cpp:
#include "smart_pointer.hpp"
#include <iostream>
#include <string>using namespace std;int main(int argc, char **argv)
{Smartpointer<int>t1(new int (5));cout<<(*t1)<<endl;cout<<t1.use_count()<<endl;Smartpointer<int>t2(t1);cout<<t2.use_count()<<endl;cout<<t1.use_count()<<endl;Smartpointer<string>t3(new string ("hello world"));// Smartpointer<string>t2(t4,t3);//Smartpointer<int> static_pointer_cast(t3); //指针类型转换没有成功?Smartpointer<int>t4 = t2;cout<<(*t4)<<endl;cout<<t4.use_count()<<endl;Smartpointer<int>t5(std::move(t4));return 0;
}
7. 内存池
(1)new内存分配细节
①new分配内存实际是调用malloc函数进行内存分配的;
②malloc实际分配内存时,不单单分配需要的内存大小,还要附加大量的附带内存,用以记录相关使用信息,包括记录分配了多少个字节(占4字节)、调试信息(占30-60字节)、边界调整(占10字节)、尾信息(4字节),也就是说除了实际分配的内存(比如给一个int变量分配4字节),还要附加70字节左右的附加内存,内存浪费很严重;尤其是频繁申请小块内存时,浪费更加严重;
③重载new操作符、重载delete操作符
void *operator new(size_t size)
{int *p = (int *)malloc(sizeof(int)):return *p;
}void *operator new[](size_t size)
{void *p = malloc(size); //内部会有转换机制,将你输入的个数,乘以类型的单位字节,算出来总数字节赋给size;return p;
}void *operator delete[](void *p)
{free(p);
}void *operator delete(void *p)
{free(p);
}
④定位new(pleacement new)
在已经分配的原始内存中初始化一个对象,相当于从之前已经分配好的大块内存中取出一块来给新的变量使用;通常应用①在硬件设备地址与c++的类直接关联;②容器也利用了预分配内存,然后逐步使用的方法;
void *p1 = (void*)new char[sizeof((A))];
A *p2 = new (p1)A(); //调用了无参构造函数,使用的是原来p1的大块内存;//自定义有参的话,就可以使用A(12)带参数了;
(2)内存池(池化技术是解决内存开销问题的,像线程池、内存池,其内部的机理是利用链表形成大内存,后面每次使用时,都分配一个结点空间给变量使用)
①作用:减少malloc次数,就减少了对内存的浪费;
②原理:用malloc分配一大块内存,当后面使用要分配时,从这一大块内存中一点一点分派,当一大块内存快用完时,再用malloc申请一大块内存,然后再一点一点分配;
③嵌入式指针,是借用A对象所占用的前8个字节(可能是2个整型数等),来充当指针,当被分配时,就指针后移,将该部分空间分配给新变量,利用内存共享,实现了空间的高效利用;
内存池的代码实现:
#include <iostream>using namespace std;class Test
{
public:Test() = default;Test(int num){m_num = num;}void * operator new(size_t size){cout<<"size = "<<size<<endl;Test *temp;if(m_head == nullptr){cout<<"malloc"<<endl;m_head = (Test *)malloc(sizeof(Test) * 50);temp = m_head;for(int i = 0; i < 50; i++){temp->next = temp + 1;temp = temp->next;}temp->next = nullptr;}cout<<"mem"<<endl;temp = m_head;m_head = m_head->next;return m_head;}void * operator new[](size_t size){cout<<"new[] "<<size<<endl;void *p = malloc(size); //这里的size会自动转换为以字节为单位的大小return p;}void operator delete(void *p){Test * temp = (Test *)p;temp->next = m_head;m_head = temp;}void operator delete[](void *p){cout<<"delete[]"<<endl;free(p);}int m_t;int m_num;Test *next;static Test *m_head;
};Test * Test::m_head = nullptr;int main(int argc, char **argv)
{Test *p1 = new Test[10]; //这里的10指的是10个Test大小的内存delete [] p1;cout<<"begin:"<<endl;Test *p2 = new Test(2); //??这里是重载new,为什么当做构造函数报错?,因为Test(2)先构造一个对象,就像int(5)先初始化一个值为5的变量Test *p3 = new Test(); //为什么已添加这个就报错?p3->m_num = 3;p3->m_t = 4;cout<<p3->m_num<<" : "<<p3->m_t<<endl;return 0;
}