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;
}