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

130 篇文章 4 订阅
92 篇文章 18 订阅

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<A *>(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()"<<endl;
	}
	~A(){
		cout<<"~A()"<<endl;
	}
};

void * A::operator new(size_t size){//size == sizeof(A),一个字节
	cout<<"A::operator new()"<<endl;
	A *p = (A *)malloc(size);
	return p;
}
void * A::operator delete(void *p){
	cout<<"A::operator delete()"<<endl;
	free(p);
}
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()"<<endl;
	}
	~A(){
		cout<<"~A()"<<endl;
	}
};

//size == 7,sizeof(A) * 3 + 4
//多出4个字节记录数组大小,知道调用构造函数和析构函数次数。
void * A::operator new[](size_t size){
	cout<<"A::operator new[]()"<<endl;
	A *p = (A *)malloc(size);
	return p;
}
void * A::operator delete[](void *p){
	cout<<"A::operator delete[]()"<<endl;
	free(p);
}
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 <cstdlib>
#include <ctime>
#include <iostream>
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一次+1
        static int m_iMallocCount; //统计malloc次数,每malloc一次+1

        A()
        {
            // 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<A *>(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<A *>(p))->next = m_FreePosi;
        m_FreePosi = static_cast<A *>(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一次+1
        static int m_iMallocCount; //统计malloc次数,每malloc一次+1

    private:
        A *next;
        static A *m_FreePosi;     //指向一块分配出去的内存首地址
        static int m_sTrunkCount; //一次分配多少倍该类的内存
    };

//#define MYMEMPOOL

    void *A::operator new(size_t size)
    {
#ifdef MYMEMPOOL
        A *p = (A *)malloc(size);
        return p;
#endif

        A *tmplink;
        if (m_FreePosi == nullptr)
        {
            m_sTrunkCount *= 2;
            size_t realsize = size * m_sTrunkCount;
            m_FreePosi = reinterpret_cast<A *>(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 MYMEMPOOL
        free(p);
        return;
#endif

        (static_cast<A *>(p))->next = m_FreePosi;
        m_FreePosi = static_cast<A *>(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 <cstdlib>
#include <ctime>
#include <iostream>
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一次+1
        static int m_iMallocCount; //统计malloc次数,每malloc一次+1

        A()
        {
            // 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<A *>(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<A *>(p))->next = m_FreePosi;
        m_FreePosi = static_cast<A *>(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一次+1
        static int m_iMallocCount; //统计malloc次数,每malloc一次+1

    private:
        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 MYMEMPOOL

    void *A::operator new(size_t size)
    {
#ifdef MYMEMPOOL
        A *p = (A *)malloc(size);
        return p;
#endif

        A *tmplink;
        if (m_FreePosi == nullptr)
        {
            m_sTrunkCount *= 2;
            size_t realsize = size * m_sTrunkCount;
            m_FreePosi = reinterpret_cast<A *>(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 MYMEMPOOL
        free(p);
        return;
#endif

        (static_cast<A *>(p))->next = m_FreePosi;
        m_FreePosi = static_cast<A *>(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 <cstdlib>
#include <ctime>
#include <iostream>
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<<mytest.m_i<<endl;

    TestEP::obj *ptmp = (TestEP::obj *)&mytest;
    ptmp->next = nullptr;
    cout<<mytest.m_i<<endl;

    cout << "Over!\n";
    return 0;
}

18.4.2 内存池代码的改进


#include <cstdlib>
#include <ctime>
#include <iostream>
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 <cstdlib>
#include <ctime>
#include <iostream>
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()"<<endl;
	}
	PLA(int tmp):m_a(tmp){
		cout<<"PLA(int)"<<endl;
	}
	~PLA()
		cout<<"~PLA()"<<endl;
	}
};

//先分配内存
void *p1 = (void *)new char[sizeof(PLA)];
//定位new,调用无参构造函数,不额外分配内存
PLA *po1 = new(p1) PLA();
po1->~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();//函数,内部没有调用malloc
	PLA::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;
	}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值