六、内存高级话题
Author: XFFer_
文章目录
01 new、delete的进一步认识
1 总述与回顾
2 从new说起
从new说起
new类对象时加不加括号的区别
class A {
public:
int m_i;
};
A* pa = new A(); //m_i = 0
A* pa_t = new A; //m_i = 随机值
- 当类有成员变量,带有括号的初始化会把一些和成员变量有关的内存清零
- 当类中有构造函数,则这两种写法得到的结果没有区别
new干了啥
new 可以叫关键字/操作符 fn + F12
会跳转到operator new。
可以在Debug状态下使用 调试
-> 窗口
-> 反汇编
得到如下:
这里可以看出call operator new
这个函数
这里可以看到call malloc
C语言中内存分配函数malloc(size传入堆中分配内存的大小)
这里可以看到call operator delete
这个函数
简化流程
02 new细节探秘,重载类内operator new、delete
1 new内存分配细节探秘
2 重载类内operator new和operator delete操作符
3 重载类内operator new[]和operator delete[]操作符
new内存分配探秘
先介绍一个函数 void *memset(void *s, int ch, size_t n)
。
作用是:将某一块内存中的内容全部设定为指定的值。
char* ppchar = new char[10];
memset(ppchar, 0, 10); //这里将这分配的十字节的内存初始化为0
delete[] ppchar;
内存的回收,实际上并不是只释放了分配的内存,影响的范围很广。给我一种感觉是:最初分配的多个变量的内存并不是连续的,会产生内存碎片,free为了避免碎片导致的内存没有足够的空间储存更大的信息,是一种很复杂的工作。
重载类内operator new和operator delete操作符
class A {
public:
static void* operator new(size_t size);
static void operator delete(void *phead);
};
void* A::operator new(size_t size)
{
A* ppoint = (A*)malloc(size);
return ppoint;
}
void A::operator delete(void *phead)
{
free(phead);
}
A* a = new A();
A* a_c = ::new A(); //::
如果做new前加 ::
全局操作符,那就不会调用自己重载的operator new/delete函数了,而是调用系统内部的。
重载类内operator new[]和operator delete[]操作符
和上一模块的代码很相似,当初始化类对象数组时,只会调用一次operator new[],调用数组长度的构造函数;释放时,先调用数组长度的析构函数,再调用一次operator delete[]。在分配内存时还会多分配4个字节用来储存数组的长度。
03 内存池概念、代码实现和详细分析
1 内存池的概念和实现原理概述
2 针对一个类的内存池实现演示代码
3 内存池代码后续说明
内存池的概念和实现原理概述
malloc
:内存浪费,频繁分配小块内存,浪费更加明显。
Q: 内存池要解决的问题?
A: 减少malloc
的调用次数。
内存池的实现原理: 用malloc申请一大块内存,当需要分配时,就从该内存池一点点分配给对象。
针对一个类的内存池实现演示代码
class A
{
public:
static void* operator new(size_t size);
static void operator delete(void* phead);
static int m_iCout; //分配计数统计,每new一次,就统计一次
static int m_iMallocCount; //每malloc一次,就统计一次
private:
A* next;
static A* m_FreePosi; //总是指向一块可以分配出去的内存的首地址
static int m_sTrunkCout; //一次分配多少倍的该类内存
};
int A::m_iCout = 0;
int A::m_iMallocCount = 0;
A* A::m_FreePosi = nullptr;
int A::m_sTrunkCout = 5;
void* A::operator new(size_t size)
{
A* tmplink;
if (m_FreePosi == nullptr)
{
size_t relsize = m_sTrunkCout * size;
//申请内存池的大小
m_FreePosi = reinterpret_cast<A*>(new char[relsize]);
//传统new,调用系统底层的malloc,char是1字节,这里开辟了relsize个字节的内存空间
tmplink = m_FreePosi;
//把分配出的一大块内存彼此链起来供后续使用
for (;tmplink != &m_FreePosi[m_sTrunkCout -1]; ++tmplink)
{
tmplink->next = tmplink + 1;
//这里对tmplink->next的改动,实际上也是对m_FreePosi的改动,进入循环前地址传递
}
tmplink->next = nullptr;
++m_iMallocCount;
}
tmplink = m_FreePosi;
m_FreePosi = m_FreePosi->next; //把内存池中的下一个指针给m_FreePosi用于下次分配
++m_iCout;
return tmplink;
}
void A::operator delete(void *phead)
{
(static_cast<A*>(phead))->next = m_FreePosi;
m_FreePosi = static_cast<A*>(phead);
}
这里介绍一个库 #include <ctime>
单位:ms
clock_t start, end;
start = clock();
end = clock();
cout << "start和end两条语句之间夹着的所有语句的运行时间是:" << end - start << endl;
04 嵌入式指针概念及范例、内存池改进版
1 嵌入式指针
2 内存池代码的改进
嵌入式指针(embedded pointer)
一般应用在内存池中,为了节省下每次指向下个内存池中的内存地址的指针
嵌入式指针工作原理: 借用对象所占用内存空间的前4个字节,这4个字节用来链住空闲的内存块;一旦某一块被分配出去,这4个字节就可以正常使用。
class Test_Embed {
public:
int a;
int b; //为了凑4字节
struct obj
{
struct obj* next; //这个next就是个嵌入式指针
};
};
Test_Embed::obj* ptemp;
Test_Embed test;
ptemp = (Test_Embed::obj*)&test; //将类对象的首地址也就是前4个字节的首地址给嵌入式指针
ptemp->next = nullptr; //这里的nullptr用下一个内存块的首地址就可以实现链表
内存池代码的改进
class myallocator
{
public:
void *allocate(size_t size)
{
obj *tmplink;
if (m_FreePosi == nullptr) //if内的内容就是在做链接
{
size_t realsize = m_sTrunkCout * size;
m_FreePosi = (obj*)malloc(realsize); //返回值void*万能指针
tmplink = m_FreePosi; //让tmplink这个嵌入式指针指向开辟内存的首地址
for (int i = 0; i < m_sTrunkCout - 1; ++i) //一直链接到最后一个内存块
{
tmplink->next = (obj*)((char*)tmplink + size); //指向下一个内存块的首地址
tmplink = tmplink->next;
}
tmplink->next = nullptr; //最后一个内存块的next指针指向空用来下一次调用if,malloc一个新的内存池
}
//现在整个内存池的每个block都已经链接好了,next指针就指向下一个内存块
templink = m_FreePosi;
m_FreePosi = m_FreePosi->next; //跟踪可用内存块
return tmplink;
}
void deallocate(void* phead)
{
((obj*)phead)->next = m_FreePosi;
m_FreePosi = (obj*)phead;
}
private:
struct obj
{
struct obj* next;
};
int m_sTrunkCout = 5;
obj* m_FreePosi = nullptr;
};
//如何使用
class useit {
public:
int m_i;
int m_o; //这两个纯属为了使类对象>4字节
public:
static myallocator myalloc; //在下部定义
static void* operator new (size_t size)
{
return myalloc.allocate(size); //返回的是一个指针
}
static void operator delete (void* tmp)
{
myalloc.deallocate(tmp);
}
};
myallocator useit::myalloc; //定义静态变量
//用宏简写
#define DECLEAR_POOLING_ALLOC() \
public:\
static myallocator myalloc;\
static void* operator new (size_t size)\
{\
return myalloc.allocate(size);\
}\
static void operator delete (void* tmp)\
{\
myalloc.deallocate(tmp);\
}
#define IMPLEMENT_POOL_ALLOC(classname) \
myallocator classname::myalloc
class A {
DECLEAR_POOLING_ALLOC()
public:
int m_j;
int m_k;
};
IMPLEMENT_POOL_ALLOC(A);
05 重载new、delete,定位new及重载
1 定位new (placement new)
2 多种版本的operator new重载
定位new(placement new)
没有placement delete
功能: 在已经分配的原始内存中初始化一个对象。
格式: new (地址) 类对象()
//定位new也可以重载
void* operator new (size_t size, void* ppoint)
{
return ppoint;
}