《C++新经典》第18章 内存高级话题
创始人
2024-03-16 17:05:13
0

《C++新经典》第18章 内存高级话题

  • 18.1 new、delete的进一步认识
    • 18.1.2 从new说起
  • 18.2 new内存分配细节探秘与重载类内operator new、delete
    • 18.2.1 new内存分配细节探秘
    • 18.2.2 重载类内operator new、operator delete操作符
    • 18.2.3 重载类内operator new[]、operator delete[]操作符
  • 18.3 内存池概念、代码实现和详细分析
    • 18.3.1 内存池的概念和实现原理简介
    • 18.3.2 内存池实现演示代码
    • 18.3.3 内存池代码后续说明
  • 18.4 嵌入式指针概念及范例、内存池改进版
    • 18.4.1 嵌入式指针
    • 18.4.2 内存池代码的改进
  • 18.5 重载全局new/delete、定位new及重载
    • 18.5.1 重载全局operator new/delete ([]) 操作符
    • 18.5.2 定位new(placement new)
    • 18.5.3 多种版本的operator new 重载

18.1 new、delete的进一步认识

18.1.2 从new说起

  1. new对象时有无括号的区别
class A {
public:
};A *pa = new A();
A *pa2 = new A;

(1)空类,两者无区别;
(2)若类A有成员变量(int m_i;),有括号初始化为0,无括号随机值。
(3)若类A有构造函数(A(){}),初始化工作交给构造函数自己做(构造函数空,随机值)。

int *p1 = new int;//随机值
int *p2 = new int();//0
int *p3 = new int(100);//100
  1. new做的事情

new关键字或者操作符,主要做了两件事。
调用operator new()函数(内部调用了malloc()函数)和调用类的构造函数。

A *pa = new A();	//操作数operator new();	//函数malloc();	//c风格函数分配内存A::A();			//有构造函数就调用构造函数
delete pa;A::~A();			//存在析构函数,则先调用析构函数operator delete();	//函数free();			//c风格函数释放内存

new与malloc区别:
(1)new是关键字/操作符,malloc是函数;
(2)new对象时,分配内存+调用构造函数(若存在);
(3)new A(),可以成员变量初始化。

delete与free区别:
(1)delete是关键字/操作符,free是函数;
(2)delete对象时,释放内存+调用析构函数(若存在);

  1. malloc做了什么事

new最终通过调用malloc来分配内存。

18.2 new内存分配细节探秘与重载类内operator new、delete

18.2.1 new内存分配细节探秘

char *p = new char[10]; //new char[10](0);
memset(p, 0, 10);
delete[] p;

malloc分配内存周围记录了很多其他内容,记录分配的字节数等。
free内存块,包括合并数据块、登记空闲块的大小、设置空闲块首位的一些标记以方便下次分配等。

malloc分配可能的内存布局:
记录分配字节数(4字节)
Debug模式下可能的调试信息(几十字节)
实际分配字节
其他必要信息,边界调整的字节填充等(几十字节)
回收内存时内存尾标记的尾信息(4字节)

18.2.2 重载类内operator new、operator delete操作符

A *pa = new A();	//操作数operator new();	//函数malloc();	//c风格函数分配内存A::A();			//有构造函数就调用构造函数

等价于

void *tmp = operator new(sizeof(A));
A *pa = static_cast(tmp);
pa->A::A();	
delete pa;A::~A();			//存在析构函数,则先调用析构函数operator delete();	//函数free();			//c风格函数释放内存

等价于

pa->A::A();	
operator delete(pa);
class A{
public://无static似乎也行(估计编译器内部处理)static void * operator new(size_t size);static void * operator delete(void *p);A(){cout<<"A()"<cout<<"~A()"<//size == sizeof(A),一个字节cout<<"A::operator new()"<cout<<"A::operator delete()"<
A *pa1 = new A();
delete pa1;//::作用域运算符
//调用全局new和delete关键字
A *pa2 = ::new A();
::delete pa2;

18.2.3 重载类内operator new[]、operator delete[]操作符

class A{
public://无static似乎也行(估计编译器内部处理)static void * operator new[](size_t size);static void * operator delete[](void *p);A(){cout<<"A()"<cout<<"~A()"<cout<<"A::operator new[]()"<cout<<"A::operator delete[]()"<
A *pa1 = new A[3]();
delete[] pa1;//输出
/*
A::operator new[]()
A()
A()
A()
~A()
~A()
~A()
A::operator delete[]()
*/ 

18.3 内存池概念、代码实现和详细分析

18.3.1 内存池的概念和实现原理简介

malloc频繁分配小块内存时,浪费明显。

内存池解决的主要问题:

  • 减少malloc调用次数,可以减少内存浪费;
  • 减少malloc调用次数,稍微提升程序的运行效率或者运行速度;

内存池的实现原理是用malloc申请一大块内存,使用时一点点分配。不够时,重新分配更大一块内存,然后一点点分配。涉及到内存如何一小块分割和回收问题。

18.3.2 内存池实现演示代码


#include 
#include 
#include 
using namespace std;namespace _n1
{class A{public:static void *operator new(size_t size);static void operator delete(void *p);A(){cout << "A()\n";}~A(){cout << "~A()\n";}};void *A::operator new(size_t size){cout << "new\n";//传统实现A *p = (A *)malloc(size);return p;}void A::operator delete(void *p){cout << "delete\n";//传统实现free(p);}
}namespace _n2
{class A{public:static void *operator new(size_t size);static void operator delete(void *p);static int m_iCount;       //分配计数统计,每new一次+1static int m_iMallocCount; //统计malloc次数,每malloc一次+1A(){// cout << "A()\n";}~A(){// cout << "~A()\n";}private:A *next;static A *m_FreePosi;     //指向一块分配出去的内存首地址static int m_sTrunkCount; //一次分配多少倍该类的内存};void *A::operator new(size_t size){// cout << "new\n";A *tmplink;if (m_FreePosi == nullptr){size_t realsize = size * m_sTrunkCount;m_FreePosi = reinterpret_cast(new char[realsize]);for (tmplink = m_FreePosi; tmplink != &m_FreePosi[m_sTrunkCount - 1]; ++tmplink)tmplink->next = tmplink + 1;tmplink->next = nullptr;++m_iMallocCount;}tmplink = m_FreePosi;m_FreePosi = m_FreePosi->next;++m_iCount;return tmplink;}void A::operator delete(void *p){// cout << "delete\n";(static_cast(p))->next = m_FreePosi;m_FreePosi = static_cast(p);}int A::m_iCount = 0;int A::m_iMallocCount = 0;A *A::m_FreePosi = nullptr;int A::m_sTrunkCount = 5 * 100;
}namespace _n3
{class A{public:static void *operator new(size_t size);static void operator delete(void *p);static int m_iCount;       //分配计数统计,每new一次+1static int m_iMallocCount; //统计malloc次数,每malloc一次+1private:A *next;static A *m_FreePosi;     //指向一块分配出去的内存首地址static int m_sTrunkCount; //一次分配多少倍该类的内存};//#define MYMEMPOOLvoid *A::operator new(size_t size){
#ifdef MYMEMPOOLA *p = (A *)malloc(size);return p;
#endifA *tmplink;if (m_FreePosi == nullptr){m_sTrunkCount *= 2;size_t realsize = size * m_sTrunkCount;m_FreePosi = reinterpret_cast(new char[realsize]);for (tmplink = m_FreePosi; tmplink != &m_FreePosi[m_sTrunkCount - 1]; ++tmplink)tmplink->next = tmplink + 1;tmplink->next = nullptr;++m_iMallocCount;}tmplink = m_FreePosi;m_FreePosi = m_FreePosi->next;++m_iCount;return tmplink;}void A::operator delete(void *p){
#ifdef MYMEMPOOLfree(p);return;
#endif(static_cast(p))->next = m_FreePosi;m_FreePosi = static_cast(p);}int A::m_iCount = 0;int A::m_iMallocCount = 0;A *A::m_FreePosi = nullptr;int A::m_sTrunkCount = 5 * 100;
}int main()
{if (0){_n1::A *pa = new _n1::A();delete pa;cout << sizeof(_n1::A) << endl;}if (0){clock_t start = clock();for (int i = 0; i < 5000000; i++)_n2::A *pa = new _n2::A();// delete pa;clock_t end = clock();cout << "m_iCount: " << _n2::A::m_iCount << endl;cout << "m_iMallocCount: " << _n2::A::m_iMallocCount << endl;cout << "time: " << (end - start) / 1000.0 << endl;}if (1){clock_t start = clock();for (int i = 0; i < 5000000; i++)_n3::A *pa = new _n3::A();clock_t end = clock();cout << "m_iCount: " << _n3::A::m_iCount << endl;cout << "m_iMallocCount: " << _n3::A::m_iMallocCount << endl;cout << "time: " << (end - start) / 1000.0 << endl;}cout << "Over!\n";return 0;
}

18.3.3 内存池代码后续说明


#include 
#include 
#include 
using namespace std;namespace _n1
{class A{public:static void *operator new(size_t size);static void operator delete(void *p);A(){cout << "A()\n";}~A(){cout << "~A()\n";}};void *A::operator new(size_t size){cout << "new\n";//传统实现A *p = (A *)malloc(size);return p;}void A::operator delete(void *p){cout << "delete\n";//传统实现free(p);}
}namespace _n2
{class A{public:static void *operator new(size_t size);static void operator delete(void *p);static int m_iCount;       //分配计数统计,每new一次+1static int m_iMallocCount; //统计malloc次数,每malloc一次+1A(){// cout << "A()\n";}~A(){// cout << "~A()\n";}private:A *next;static A *m_FreePosi;     //指向一块分配出去的内存首地址static int m_sTrunkCount; //一次分配多少倍该类的内存};void *A::operator new(size_t size){// cout << "new\n";A *tmplink;if (m_FreePosi == nullptr){size_t realsize = size * m_sTrunkCount;m_FreePosi = reinterpret_cast(new char[realsize]);for (tmplink = m_FreePosi; tmplink != &m_FreePosi[m_sTrunkCount - 1]; ++tmplink)tmplink->next = tmplink + 1;tmplink->next = nullptr;++m_iMallocCount;}tmplink = m_FreePosi;m_FreePosi = m_FreePosi->next;++m_iCount;return tmplink;}void A::operator delete(void *p){// cout << "delete\n";(static_cast(p))->next = m_FreePosi;m_FreePosi = static_cast(p);}int A::m_iCount = 0;int A::m_iMallocCount = 0;A *A::m_FreePosi = nullptr;int A::m_sTrunkCount = 5 * 100;
}namespace _n3
{class A{public:static void *operator new(size_t size);static void operator delete(void *p);static int m_iCount;       //分配计数统计,每new一次+1static int m_iMallocCount; //统计malloc次数,每malloc一次+1private:A *next;static A *m_FreePosi;     //指向一块分配出去的内存首地址static int m_sTrunkCount; //一次分配多少倍该类的内存//释放内存池class GC{public:~GC(){cout << "~GC()\n";while (A::m_FreePosi){A *tmp = A::m_FreePosi;free((void *)A::m_FreePosi);A::m_FreePosi = tmp->next;}}};static GC gc;};//#define MYMEMPOOLvoid *A::operator new(size_t size){
#ifdef MYMEMPOOLA *p = (A *)malloc(size);return p;
#endifA *tmplink;if (m_FreePosi == nullptr){m_sTrunkCount *= 2;size_t realsize = size * m_sTrunkCount;m_FreePosi = reinterpret_cast(new char[realsize]);for (tmplink = m_FreePosi; tmplink != &m_FreePosi[m_sTrunkCount - 1]; ++tmplink)tmplink->next = tmplink + 1;tmplink->next = nullptr;++m_iMallocCount;}tmplink = m_FreePosi;m_FreePosi = m_FreePosi->next;++m_iCount;return tmplink;}void A::operator delete(void *p){
#ifdef MYMEMPOOLfree(p);return;
#endif(static_cast(p))->next = m_FreePosi;m_FreePosi = static_cast(p);}int A::m_iCount = 0;int A::m_iMallocCount = 0;A *A::m_FreePosi = nullptr;int A::m_sTrunkCount = 5 * 100;A::GC A::gc;
}int main()
{if (0){_n1::A *pa = new _n1::A();delete pa;cout << sizeof(_n1::A) << endl;//_n1::A a;}if (0){clock_t start = clock();for (int i = 0; i < 5000000; i++)_n2::A *pa = new _n2::A();// delete pa;clock_t end = clock();cout << "m_iCount: " << _n2::A::m_iCount << endl;cout << "m_iMallocCount: " << _n2::A::m_iMallocCount << endl;cout << "time: " << (end - start) / 1000.0 << endl;}if (0){clock_t start = clock();for (int i = 0; i < 5000000; i++)_n3::A *pa = new _n3::A();clock_t end = clock();cout << "m_iCount: " << _n3::A::m_iCount << endl;cout << "m_iMallocCount: " << _n3::A::m_iMallocCount << endl;cout << "time: " << (end - start) / 1000.0 << endl;}cout << "Over!\n";return 0;
}

18.4 嵌入式指针概念及范例、内存池改进版

18.4.1 嵌入式指针

  1. 嵌入式指针概念
    embedded pointer

类A中存在成员变量A *next;当一个空闲内存块分配出去后,next指向无实际意义(不需要了),可以被覆盖,另作他用,节省字节。

next占用4个字节,sizeof(A)必须大于等于4。

  1. 嵌入式指针演示代码

#include 
#include 
#include 
using namespace std;class TestEP
{
public:int m_i = 5;int m_j;struct obj{struct obj *next;};
};int main()
{cout << sizeof(TestEP) << endl;TestEP mytest;cout<next = nullptr;cout<

18.4.2 内存池代码的改进


#include 
#include 
#include 
using namespace std;//专门的内存池类或内存分配器
//使用本类的类sizeof()必须大于等于4
class myallocator
{
public://释放内存池~myallocator(){while (m_FreePosi){obj *tmp = m_FreePosi;m_FreePosi = m_FreePosi->next;free((void *)tmp);}}//分配内存接口void *allocate(size_t size){obj *tmplink;if (m_FreePosi == nullptr){size_t realsize = m_sTrunkCout * size;m_FreePosi = (obj *)malloc(realsize);tmplink = m_FreePosi;for (int i = 0; i < m_sTrunkCout - 1; ++i){tmplink->next = (obj *)((char *)tmplink + size);tmplink = tmplink->next;}tmplink->next = nullptr;m_sTrunkCout *= 2;}tmplink = m_FreePosi;m_FreePosi = m_FreePosi->next;return tmplink;}//释放内存接口void deallocate(void *p){((obj *)p)->next = m_FreePosi;m_FreePosi = (obj *)p;}private://类内结构,只能类内使用struct obj{struct obj *next; // next就是嵌入式指针};int m_sTrunkCout = 5;obj *m_FreePosi = nullptr;
};class A
{
public://必须保证sizeof(A)凑够4字节int m_i;int m_j;public:static myallocator myalloc;static void *operator new(size_t size){return myalloc.allocate(size);}static void operator delete(void *p){myalloc.deallocate(p);}
};
myallocator A::myalloc;int main()
{{cout << sizeof(myallocator) << endl;cout << sizeof(A) << endl;A *mypa[100];for (int i = 0; i < 15; ++i){mypa[i] = new A();printf("%p\n", mypa[i]);}for (int i = 0; i < 15; ++i){delete mypa[i];}}cout << "Over!\n";return 0;
}

#include 
#include 
#include 
using namespace std;//专门的内存池类或内存分配器
//使用本类的类sizeof()必须大于等于4
class myallocator
{
public://释放内存池~myallocator(){while (m_FreePosi){obj *tmp = m_FreePosi;m_FreePosi = m_FreePosi->next;free((void *)tmp);}}//分配内存接口void *allocate(size_t size){obj *tmplink;if (m_FreePosi == nullptr){size_t realsize = m_sTrunkCout * size;m_FreePosi = (obj *)malloc(realsize);tmplink = m_FreePosi;for (int i = 0; i < m_sTrunkCout - 1; ++i){tmplink->next = (obj *)((char *)tmplink + size);tmplink = tmplink->next;}tmplink->next = nullptr;m_sTrunkCout *= 2;}tmplink = m_FreePosi;m_FreePosi = m_FreePosi->next;return tmplink;}//释放内存接口void deallocate(void *p){((obj *)p)->next = m_FreePosi;m_FreePosi = (obj *)p;}private://类内结构,只能类内使用struct obj{struct obj *next; // next就是嵌入式指针};int m_sTrunkCout = 5;obj *m_FreePosi = nullptr;
};#define DECLARE_POOL_ALLOC()               \
public:                                    \static void *operator new(size_t size) \{                                      \return myalloc.allocate(size);     \}                                      \static void operator delete(void *p)   \{                                      \myalloc.deallocate(p);             \}                                      \static myallocator myalloc;#define IMPLEMENT_POOL_ALLOC(classname) \myallocator classname::myalloc;class A
{DECLARE_POOL_ALLOC();public://必须保证sizeof(A)凑够4字节int m_i;int m_j;
};
IMPLEMENT_POOL_ALLOC(A);int main()
{{cout << sizeof(myallocator) << endl;cout << sizeof(A) << endl;A *mypa[100];for (int i = 0; i < 15; ++i){mypa[i] = new A();printf("%p\n", mypa[i]);}for (int i = 0; i < 15; ++i){delete mypa[i];}}cout << "Over!\n";return 0;
}

18.5 重载全局new/delete、定位new及重载

18.5.1 重载全局operator new/delete ([]) 操作符

//重载全局operator new
void * operator new(size_t size){return malloc(size);
}//重载全局operator new[]
void * operator new[](size_t size){return malloc(size);
}//重载全局operator delete
void * operator delete(void * p){return free(p);
}//重载全局operator delete[]
void * operator delete[](void * p){return free(p);
}int *pi = new int(12);
delete pi;
char *pc = new char[10](0);
delete[] pc;A *p = new A();
delete p;
A *pa = new A[3]();
delete[] pa;

一般重载类中operator new/delete ([])。

18.5.2 定位new(placement new)

placement new无对应的placement delete,在已经分配的原始内存中初始化一个对象。

  • 定位new不分配内存,使用定位new前内存必须先分配好;
  • 初始化对象,就是调用对象的构造函数。
new(地址) 类类型(参数);
class PLA{
public:int m_a;PLA():m_a(0){cout<<"PLA()"<cout<<"PLA(int)"<~PLA();//根据需要调用析构函数
delete[](void *)po1;//释放内存
//delete[] p1;//同上//先分配内存
void *p2 = (void *)new char[sizeof(PLA)](0);
//定位new,调用有参构造函数,不额外分配内存
PLA *po2 = new(p2) PLA(12);
po2->~PLA();//根据需要调用析构函数
delete[](void *)po2;//释放内存
//delete[] p2;//同上

定位new调用关系

PLA *pa = new(已经分配好的内存首地址) PLA();//定位new操作符operator new();//函数,内部没有调用mallocPLA::PLA();//调用构造函数

定位new调用的operator new操作符的重载代码。

public:void * operator new(size_t, void *p){//多一个p,指向已经分配好的内存首地址return p;}

18.5.3 多种版本的operator new 重载

第一个参数固定size_t(sizeof(对象)),其他参数指定。

//无内存分配,无类构造函数调用
PLA *po2 = new(1234, 56) PLA(12);public://第一个参数,系统默认传递sizeof(PLA)void * operator new(size_t size, int t1, int t2){return NULL;}

会出现警告:void *PLA::operator new(size_t, int, int)未找到匹配的删除运算符。

可以增加对应的operator delete重载以避免警告(非必需)。

public:void  operator delete(void* p, int t1, int t2){return;}

相关内容

热门资讯

关于海南育儿补贴制度实施热点问... 户籍刚迁入海南的婴幼儿是否能享受育儿补贴;如何快速了解补贴申领流程……12月25日,海南省新闻办公室...
宋朝的中介制度 走进《清明上河图》中汴河两岸喧嚷的市集,除了林立的店铺与往来的舟车,还有一种身影穿梭其间——他们并非...
《西安市地下水条例》《西安历史... 央广网西安12月26日消息(记者侯凯奇)12月25日,西安市人大常委会召开新闻发布会,正式公布《西安...
三部门发文完善幼儿园收费政策 ... 近年来,人民群众对适龄儿童“上得起”“上好园”的诉求越来越强烈,幼儿园收费政策需要与时俱进。近日,为...
下一阶段货币政策如何发力?央行... 中国网财经12月26日讯 近日中国人民银行货币政策委员会召开2025年第四季度例会,分析国内外经济金...
跨省盗杀家犬 涉嫌犯罪终落网 山西晚报·山河+讯(记者 辛戈)套牌的汽车、70余支已经装填或待用的毒针、50余颗用剧毒物质自制的药...
政策宣传面对面 就业服务进社区 本报讯(记者 史治国 通讯员 张冬梅)“实实在在的就业创业政策、周边最新的招聘信息,有需要的都看过来...
300元就能看光员工的微信聊天... 据每日经济新闻报道,一款面向公司用户、单套价格或不足300元的监控软件正在被公开售卖。 记者深入调查...
罕见!深交所凌晨发函,剑指向日... 向日葵(300111)又摊上事儿了!12月26日凌晨,深交所对向日葵下发关注函。 2025年9月2...
阅读中的风景与旅途中的阅读:《... 2025年12月,《全民阅读促进条例》(以下简称《条例》)正式颁布,定于2026年2月1日起施行。这...