侯捷老师 C++内存管理-第一讲 学习笔记

第一讲 primitives

primitives 指管理内存的 “基础工具”

一、内存的分配和释放

在这里插入图片描述

由于 O.S API过于底层,已经与操作系统绑定,可移植性差

该课程研究前四个层次

在这里插入图片描述

  • malloc() 与 free() :分配与释放内存

  • new 与 delete:

    • new (分配内存,调用构造)
    • delete(调用析构,释放内存)
  • ::operator new() 与 ::operator delete() :

    • ::operator new(),全域中的operator new(), 实现依靠 malloc(), 分配内存
    • ::operator delete() ,全域中的operator delete(),实现依靠 free(),释放内存
  • allocator:分配器

下面将具体介绍这些函数的使用方法。对于不同的编译器,其allocator函数的接口也有所不同:

在这里插入图片描述

对于GNU C,不同版本又有所不同:

preview

这张图中的__gnu_cxx::__pool_alloc< T >().allocate()对应于上张图中的alloc::allocate()。这是由于GNU版本的不同

侯捷源码:

#include <iostream>
#include <complex>
#include <memory>                //std::allocator  
//#include <ext\pool_allocator.h>    
//GCC使用,欲使用 std::allocator 以外的 allocator, 就得自行 #include <ext/...> 
using namespace std;
namespace jj01
{
    void test_primitives()
    {
        cout << "\ntest_primitives().......... \n";

        void* p1 = malloc(512); //512 bytes
        free(p1);

        complex<int>* p2 = new complex<int>; //one object
        delete p2;

        void* p3 = ::operator new(512); //512 bytes
        ::operator delete(p3);

        //以下使用 C++ 标准库提供的 allocators。
        //其接口虽有标准规格,但实现厂商尚未完全遵守;下面三者形式略异。
#ifdef _MSC_VER
        //以下两都是 non-static,定要通過 object 調用。以下分配 3 個 ints.
        int* p4 = allocator<int>().allocate(3, (int*)0);
        allocator<int>().deallocate(p4, 3);
#endif
#ifdef __BORLANDC__
        //以下两函数都是 non-static,定要通过 object 调用。以下分配 5 个 ints.
        int* p4 = allocator<int>().allocate(5);
        allocator<int>().deallocate(p4, 5);
#endif
#ifdef __GNUC__
        //以下两函数都是 static,可通过全名调用之。以下分配 512 bytes.
        //void* p4 = alloc::allocate(512); 
        //alloc::deallocate(p4,512);   

        //以下两函数都是 non-static,定要通过 object 调用。以下分配 7 个 ints.    
        void* p4 = allocator<int>().allocate(7);
        allocator<int>().deallocate((int*)p4, 7);

        //以下两函数都是 non-static,定要通过 object 调用。以下分配 9 个 ints.  
        void* p5 = __gnu_cxx::__pool_alloc<int>().allocate(9);
        __gnu_cxx::__pool_alloc<int>().deallocate((int*)p5, 9);
#endif
    }
} //namespace

int main(void)
{
    jj01::test_primitives();
    return 0;
}

二、基本构件之 new/delete expression

1、new expression

preview

new expression的实质:

  • 1、通过调用operator new()分配内存,其中operator new()的内部是调用了malloc()函数

  • 2、调用构造方法构造对象

    注意,只有编译器可以直接通过pc->Complex::Complex(1, 2)这样的方法调用ctor

    我们这样做将产生错误,需要通过placement new 来调用构造函数。

2、delete expression

preview

delete expression 的实质:

  • 1、调用析构函数,销毁对象

  • 2、调用operator delete()函数释放内存,其中 operator delete()的内部调用了free()函数

测试直接调用 Ctor & Dtor:

#include <iostream>
#include <string>
//#include <memory>              //std::allocator  
using namespace std;

namespace jj02
{
    class A
    {
    public:
        int id;

        A() : id(0) { cout << "default ctor. this=" << this << " id=" << id << endl; }
        A(int i) : id(i) { cout << "ctor. this=" << this << " id=" << id << endl; }
        ~A() { cout << "dtor. this=" << this << " id=" << id << endl; }
    };

    void test_call_ctor_directly()
    {
        cout << "\ntest_call_ctor_directly().......... \n";

        string* pstr = new string;
        cout << "str= " << *pstr << endl;
        //! pstr->string::string("jjhou");  
        //[Error] 'class std::basic_string<char>' has no member named 'string'
        //! pstr->~string();    //crash -- 其語法語意都是正確的, crash 只因為上一行被 remark 起來嘛.  
        cout << "str= " << *pstr << endl;
            //------------

        A* pA = new A(1);           //ctor. this=000307A8 id=1
        cout << pA->id << endl;     //1
        pA->A::A(3);				//in VC6 : ctor. this=000307A8 id=3
        //! pA->A::A(3);                
        //in GCC : [Error] cannot call constructor 'jj02::A::A' directly
        cout << pA->id << endl;

        A::A(5);		//in VC6 : ctor. this=0013FF60 id=5 dtor. this=0013FF60    
        //! A::A(5);                    
        //in GCC : [Error] cannot call constructor 'jj02::A::A' directly
        //         [Note] for a function-style cast, remove the redundant '::A'

        cout << pA->id << endl;     //in VC6 : 3
        //in GCC : 1    

        delete pA;                  //dtor. this=000307A8 

        //simulate new
        void* p = ::operator new(sizeof(A));
        cout << "p=" << p << endl;  //p=000307A8
        pA = static_cast<A*>(p);
        pA->A::A(2);
        //! pA->A::A(2);                //in VC6 : ctor. this=000307A8 id=2
        //in GCC : [Error] cannot call constructor 'jj02::A::A' directly    

        cout << pA->id << endl;     //in VC6 : 2
        //in GCC : 0    

        //simulate delete
        pA->~A();                   //dtor. this=000307A8 
        ::operator delete(pA);      //free()
    }
} //namespace

int main(void)
{
    jj02::test_call_ctor_directly();
    return 0;
}

编译运行结果如下:

img

VC下可以直接通过内存空间调用构造函数,但测试在GNU C下无法通过,具体的内容可见代码注解和打印效果。

三、array new

preview

上图主要展示的是关于 array new 内存分配的大致情况。

解释:

  • 在实际new的时候,编译器会分配比你想象更多内存(如下图),先来看看block size 部分,也就是我们实际需要的内存。

  • 当使用new[] 来分配内存,而使用delete来释放内存,对于对象中没有指针类型的数据或者析构函数没有意义,或许没有什么影响

  • 但对于像标准库中的string一样,string对象中存放着指针指向其他内存空间或者析构函数是有意义的,若使用delete则只会调用一次析构函数,即str[1]会被析构,而str[2]、str[3]的析构函数不会被调用,这里就会导致内存泄漏

因此,当使用new[] 来分配内存时,需要使用delete[]来释放内存。并且养成良好的编程习惯,new/delete 与 new[]/delete[] 成对出现

在debug模式下:

在这里插入图片描述

下面演示array new 与 array delete:

img

#include <iostream>
#include <new>      //placement new
using namespace std;

namespace jj03
{
    class A
    {
    public:
        int id;

        A() : id(0) { cout << "default ctor. this=" << this << " id=" << id << endl; }
        A(int i) : id(i) { cout << "ctor. this=" << this << " id=" << id << endl; }
        ~A() { cout << "dtor. this=" << this << " id=" << id << endl; }
    };

    void test_array_new_and_placement_new()
    {
        cout << "\ntest_placement_new().......... \n";

        size_t size = 3;

        {
            //case 1
            //模擬 memory pool 的作法, array new + placement new. 崩潰 

            A* buf = (A*)(new char[sizeof(A)*size]);
            A* tmp = buf;

            cout << "buf=" << buf << "  tmp=" << tmp << endl;

            for (int i = 0; i < size; ++i)
                new (tmp++) A(i);           //3次 调用ctor 使用了placement new

            cout << "buf=" << buf << "  tmp=" << tmp << endl;

            //! delete [] buf;      //crash. why?
            //因為這其實是個 char array,看到 delete [] buf; 編譯器會企圖喚起多次 A::~A. 
            // 但 array memory layout 中找不到與 array 元素個數 (本例 3) 相關的信息, 
            // -- 整個格局都錯亂 (從我對 VC 的認識而言),於是崩潰。 
            delete buf;         //dtor just one time, ~[0]  

            cout << "\n\n";
        }

        {
            //case 2
            //回頭測試單純的 array new

            A* buf = new A[size];  //default ctor 3 次. [0]先於[1]先於[2])
            //A必須有 default ctor, 否則 [Error] no matching function for call to 'jj02::A::A()'
            A* tmp = buf;

            cout << "buf=" << buf << "  tmp=" << tmp << endl;

            for (int i = 0; i < size; ++i)
                new (tmp++) A(i);       //3次 ctor 使用了placement new

            cout << "buf=" << buf << "  tmp=" << tmp << endl;

            delete[] buf;    //dtor three times (次序逆反, [2]先於[1]先於[0])   
        }

        {
            //case 3    
            //掌握崩潰原因, 再次模擬 memory pool作法, array new + placement new.    
            //不, 不做了, 因為 memory pool 只是供應 memory, 它並不管 construction, 
            //也不管 destruction. 它只負責回收 memory. 
            //所以它是以 void* 或 char* 取得 memory, 釋放 (刪除)的也是 void* or char*.  
            //不像本例 case 1 釋放 (刪除) 的是 A*. 
            //
            //事實上 memory pool 形式如 jj04::test 
        }
    }
} //namespace

int main(void)
{
    jj03::test_array_new_and_placement_new();
    return 0;
}

编译运行结果如下:

img

观察,构造函数与析构函数的调用顺序,

使用new[] 与delete 的情况

使用new[] 与delete[] 的情况

接下来将更具体地展示array new 对象的内存分配情况:

img

​ 使用new[]分配10个int内存,内存分配如上图所示,首先内存块会有一个头、尾Cookie记录整个内存大小,黄色部分为debug信息,灰色部分才是真正使用到的内存,蓝色部分的12 bytes是为了让该内存块为16字节边界而补充的填充块。在这个例子中delete pi和delete[] pi效果是一样的,因为int没有析构函数。但是下面的例子就不一样了:

Cookie:其中cookie的最后一位用来表示该内存块的状态,

1 表示 已经分配

0 表示 没有分配

img

在debug模式下,上图通过new[]申请3个Demo空间大小,内存块使用了96 bytes,

计算方法:黄色部分调试信息32 + 4 = 36 bytes;黄色部分下面的“3”用于标记实际分配给对象内存个数,这里是三个所以里面内容为3,占用4 bytes;Demo内有三个int类型成员变量,一个Demo消耗内存3 * 4 = 12 bytes,由于有三个Demo,所以消耗了12 * 3 = 36 bytes空间;到目前为止消耗36 + 4 + 36 = 76 bytes,加上头尾Cookie一共8 bytes一共消耗84 bytes,由于需要16 bytes对齐,所以填充蓝色部分为12 bytes,一共消耗了84 + 12 = 96 bytes。

假设该对象的析构函数有意义,则这里释放内存时需要使用delete[],并且通过内存分配中的标记“3”,编译器将释放三个Demo对象空间,如果不加就会报错。

四、placement new

img

注意:placement new 根本没有分配 memory,而是直接使用已经分配的内存块,

所以也没有所谓真正的placement delete,只是将与placement new 对应的delete称为placement delete

五、重载

1、C++内存分配的途径

preview

在类中没有重载operator new()时,走的是第二条路线,如果在类中重载了operator new(),那么走的是第一条路线,但最后还是要调用到系统的::operator new()函数,这在后续的例子中会体现。

一般不会在全域中重载 operator new(),operator delete()

img

2、重载new 和 delete

注意:重载的new 和 delete 必须为 static,因为它的作用是分配内存并构造对象,而此时对象不存在,因此需要为static。

案例中没有为static是因为编译器默认为static,但是最好显示声明为static,助于理解

img

​ 上面这张图演示了如何在全域中重载 operator new()函数,该方法最后也是模拟了系统的做法,效果和系统的方法一样,但一般不推荐在全域中重载 operator new()函数,因为它对全局有影响,如果使用不当将造成很大的问题。

​ 如果是在类中重载operator new()方法,那么该方法有N多种形式,但必须保证函数参数列表第一个参数是size_t类型变量;对于operator delete(),第一个参数必须是void* 类型,第二个参数size_t是可选项,甚至更多参数

在这里插入图片描述

对于operator new[]和operator delete[]函数的重载,和前面类似。

在这里插入图片描述

重载placement new

在这里插入图片描述

测试案例:

#ifndef __MYFOO__
#define __MYFOO__
#include <iostream>

using namespace std;

class Bad {};

class Foo {
public:
    int _id;
    long _data;
    string _str;

public:
    Foo() : _id(0) { cout << "default ctor. this = " << this << " id = " << _id << endl; }
    // 模拟在调用operator new分配内存后,调用构造函数时出现异常并抛出,造成内存泄露,调用对应的“placement delete”
    Foo(int i) : _id(i) { cout << "ctor. this = " << this << " id = " << _id << endl; throw Bad(); }
    virtual ~Foo() { cout << "dtor. this = " << this << " id = " << _id << endl; }

    // size_t: unsigned int
    // 根据new的作用,分配内存然后构造,所以在对象构造前调用operator new,故使其为static
    static void* operator new(size_t size);
    static void  operator delete(void* p, size_t size);
    static void* operator new[](size_t size);
    static void  operator delete[](void* p, size_t size);

    // placement new
    // 第一参数必须为size_t
    static void* operator new(size_t size, void* start);
    static void  operator delete(void*,void*);
};

void* Foo::operator new(size_t size)
{
    Foo* p = (Foo*)malloc(size);
    cout << "Foo::operator new(), size = " << size << " return: " << p << endl;
     return p;
}

void Foo::operator delete(void* p, size_t size)
{
    cout << "operator delete(), p = " << p << " size = " << size << endl;
    free(p);
}

void* Foo::operator new[](size_t size)
{
    Foo* p = (Foo*)malloc(size);
    cout << "Foo::operator new[](), size = " << size << " return: " << p << endl;
    return p;
}

void Foo::operator delete[](void* p, size_t size)
{
    cout << "operator delete(), p = " << p << " size = " << size << endl;
    free(p);
}

void* Foo::operator new(size_t size, void* start)
{
    cout << "Foo::operator new(size_t, void*), size = " << size << " return: " << start << endl;
    return start;
}

void Foo::operator delete(void* p, void*)
{
    cout << "operator delete(void* p, void*), p = " << p << " size = " << sizeof(p) << endl;
    free(p);
}

#endif

void test_new_and_array_new()
{
    cout << "test_new_and_array_new()......" << endl;
    cout << "sizeof(Foo) = " << sizeof(Foo) << endl;

    Foo* p = new Foo();
    delete p;

    Foo* pArray = ::new Foo[5];
    delete[] pArray;
}

void test_placement_new()
{
    cout << "test_placement_new()......." << endl;
    Foo start;

    Foo* p1 = new Foo;
    Foo* p2 = new(&start) Foo;
    Foo* p3 = new(&start) Foo(1);
    Foo* p4 = new Foo(1);
}

int main()
{
    test_new_and_array_new();
    test_placement_new();			// 测试时,两个函数请单独调用,观察结果

    system("pause");
    return 0;
}

注意:在Foo的带参构造中,故意抛出异常,来测试placement delete

只有在 调用构造函数时出现异常并抛出,造成内存泄露,才调用对应的“placement delete”

防止,在使用placement new时,由于内存已经分配完成并传入,而构造函数出现异常并抛出,此时造成分配好的内存会被泄漏,因此此时会调用"placement delete"

注意观察测试test_placement_new()

test_new_and_array_new()

在这里插入图片描述

test_placement_new()

在这里插入图片描述

关于标准库中basic_string的new(extra)扩充申请量

在这里插入图片描述

原因:标准库的string多了一组 reference counting

3、allocator

1、per_class_allocator_1

preview

img

案例测试:

#ifndef __MYSCREEN__
#define __MYSCREEN__

#include <cstddef>
#include <iostream>

using namespace std;

//ref. C++Primer 3/e, p.765
// per-class allocator_1

// 利用内存池的观念【即创建出一大段连续空间的内存,然后将其切割成一小段一小段,物理上不一定连续但逻辑上连续】,减少使用malloc的次数,以此来避免分配cookie所占用不必要的内存
// 该版本缺点:next指针多占用了内存
// 查看版本2(AirPlane.h):使用union解决

class Screen {
public:
    Screen(int x) : i(x) { }
    int get() { return i; }

     static void* operator new(size_t);
     static void operator delete(void*);

private:
    Screen* next;
    static Screen* freeStore;
    static const int screenChunk;

private:
    int i;
};

Screen* Screen::freeStore = nullptr;
const int Screen::screenChunk = 24;

void* Screen::operator new(size_t size) 
{
    Screen* p;
    if (!freeStore) {    // 如果linked list为空,所以申请一大块内存
        size_t chunk = screenChunk * size;
        freeStore = p =
             reinterpret_cast<Screen*>(new char[chunk]);

         // 将一大块内存分成片片,当做linked list串联起来
         while ( p != &freeStore[screenChunk - 1]) {
             p->next = p + 1;
             p++;
         }
         p->next = nullptr;
     }
     p = freeStore;
     freeStore = freeStore->next;

     return p;
}

void Screen::operator delete(void* p)
{
    // 将delete object 插回 free list 前端
    (static_cast<Screen*>(p))->next = freeStore;
    freeStore = static_cast<Screen*>(p);
    // 注意:此处delete并没有调用free()函数,
    // 说明,只是将内存加入链表管理,并没有归还给操作系统系统
}

#endif

void test_per_class_allocator_1()
{
    cout << sizeof(Screen) << endl;

    size_t const N = 100;
    Screen* p[N];

    for (int i = 0; i < N; i++) {
        p[i] = new Screen(i);
    }

    // 输出前10个pointers,比较其间隔
    for (int i = 0; i < 10; i++) {
        cout << p[i] << endl;
    }

    for (int i = 0; i < 10; i++) {
        delete p[i];
    }
}

int main()
{
    test_per_class_allocator_1();
    
    system("pause");
    return 0;
}

vsCode 编译运行结果如下:

在这里插入图片描述

观察结果:每个对象间隔16 bytes

思考:当一块内存使用完后,再次分配一块新的内存,两块内存之间的连接过程。

  • 利用内存池的观念【即创建出一大段空间内存,然后将其切割成一小段一小段,物理上不一定连续但逻辑上连续】,减少使用malloc的次数,以此来避免分配cookie所占用不必要的内存
  • 该版本缺点:next指针多占用了内存
  • 查看版本2(AirPlane.h):使用union解决
2、per_class_allocator_2

preview

preview

案例如下:

#ifndef __MYAIRPLANE__
#define __MYAIRPLANE__

// pre-class allocator_2

// 解决版本1(Screen.h)缺点:通过union关键字来减少使用next而所占耗的内存
// 前两种有共同缺点,
// 就是当第一次分配的内存池使用完后,会再次分配一块大的内存,然后所有内存链接在一起,而不会归还给操作系统
// 并且当多个类时,需要重复的重载operator new
// 查看版本3(allocator.h):将该动作包装起来

class AirPlane {
private:
    struct AirPlaneRep {
        unsigned long miles;
        char type;
    };
private:
    union {
        AirPlaneRep rep;
        AirPlane* next;			// embedded pointer  
    };
public:
    unsigned long getMiles() { return rep.miles; }
    char getType() { return rep.type; }
    void set(unsigned long m, char t) {
        rep.miles = m;
        rep.type = t;
    }
public:
    static void* operator new(size_t size);
    static void operator delete(void* deadObject, size_t size);
private:
    static const int BLOCK_SIZE;
    static AirPlane* headOfFreeList;
};

AirPlane* AirPlane::headOfFreeList = nullptr;
const int AirPlane::BLOCK_SIZE = 512;

void* AirPlane::operator new(size_t size)
{
    if (size != sizeof(AirPlane)) { // 如果由于继承关系传入的size=sizeof(Airplane)大小不等于sizeof(Airplane)时,则调用::operator new!!
        return ::operator new(size);
    }

    AirPlane* p = headOfFreeList;
    if (p) {    // 已经分配空间,移动链表指针
        headOfFreeList = p->next;
    } else {    // 分配空间已空,分配一大块memory
        AirPlane* newBlock = static_cast<AirPlane*>(::operator new(BLOCK_SIZE * sizeof(AirPlane)));
        // 将大块内存分成小块内存
        for (int i = 0; i < BLOCK_SIZE; i++) {
            newBlock[i].next = &newBlock[i + 1];
        }
        newBlock[BLOCK_SIZE - 1].next = nullptr;
        p = newBlock;
        headOfFreeList = &newBlock[1];
    }
    return p;
}

void AirPlane::operator delete(void* deadObject, size_t size)
{
    if (deadObject == nullptr) return;
    if (size != sizeof(AirPlane)) {
        ::operator delete(deadObject);
        return;
    }

    AirPlane* carcass = static_cast<AirPlane*>(deadObject);
    carcass->next = headOfFreeList;
    headOfFreeList = carcass;
    // 注意:此处delete同样并没有调用free()函数,
    // 说明,只是将内存加入链表管理,并没有归还给操作系统系统
}
#endif

void test_per_class_allocator_2()
{
    cout << sizeof(AirPlane) << endl;

    size_t const N = 100;
    AirPlane* p[N];

    for (int i = 0; i < N; i++) {
        p[i] = new AirPlane;
    }

    p[1]->set(1000, 'A');
    p[5]->set(5000, 'B');
    p[9]->set(9000, 'C');
    cout << p[1] << ' ' << p[1]->getType() << ' ' << p[1]->getMiles() << endl;
  	cout << p[5] << ' ' << p[5]->getType() << ' ' << p[5]->getMiles() << endl;
  	cout << p[9] << ' ' << p[9]->getType() << ' ' << p[9]->getMiles() << endl; 

    // 输出前10个pointers,比较其间隔
    for (int i = 0; i < 10; i++) {
        cout << p[i] << endl;
    }

    for (int i = 0; i < N; i++) {
        delete p[i];
    }
}

int main()
{
    test_per_class_allocator_2();

    system("pause");
    return 0;
}

vsCode 编译运行结果如下:

在这里插入图片描述

  • 解决版本1(Screen.h)缺点:通过union关键字来减少使用next而所占耗的内存
  • 前两种有共同缺点,
    • 由于没有调用free()函数,当第一次分配的内存池使用完后,会再次分配一块大的内存,然后所有内存链接在一起,分配的内存会越来越多,但是即使这样也没有内存泄露
    • 并且当多个类时,需要重复的重载operator new
  • 查看版本3(allocator.h):将该动作包装起来
3.static allocator

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

// allocator.h
#ifndef __MYALLOCATOR__
#define __MYALLOCATOR__

// static allocator

// 将分配特定大小区块的memory allocator包装成一个class allocator
// 这样每个allocator object都是个分配器,体内维护一个freeStore lists
// 案例使用:查看 Foo.h

class allocator_ {
private:
    struct obj {
        struct obj* next;   // embedded pointer 嵌入式指针
    };
public:
    void* allocate(size_t);
    void deallocate(void*);
private:
    obj* freeStore = nullptr;
    const int CHUNK = 5;    // 标准库大小为20
};

void* allocator_::allocate(size_t size)
{
    obj* p;
    if (!freeStore) {
        size_t chunk = CHUNK * size;
        freeStore = p = (obj*)malloc(chunk);

        for (int i = 0; i < CHUNK; i++) {
            p->next = (obj*)((char*)p + size);
            p = p->next;
        }
        p->next = nullptr;
    }
    p = freeStore;
    freeStore = freeStore->next;
    return p;
}

void allocator_::deallocate(void* p)
{
    ((obj*)p)->next = freeStore;
    freeStore = (obj*)p;
}

#endif

// Foo.h
#ifndef __MYFOO__
#define __MYFOO__

#include <cstring>
#include "allocator.h"

class Foo {
public:
    long L;
    string str;
    static allocator_ myAlloc;
public:
    Foo(long l) : L(l) {}
    static void* operator new(size_t size) {
        return myAlloc.allocate(size);
    }
    static void operator delete(void* pdead) {
        return myAlloc.deallocate(pdead);
    }
};

allocator_ Foo::myAlloc;

#endif

void test_static_allocator()
{
    Foo* p[100];
    cout << "sizeof(Foo) = " << sizeof(Foo) << endl;
    cout << "sizeof(allocator_) = " << sizeof(allocator_) << endl;
    for (int i = 0; i < 23; i++) {
        p[i] = new Foo(i);
        cout << p[i] << " " << p[i]->L << endl;
    }

    for (int i = 0; i < 23; i++) {  // 比较地址间隔,注意观察每组(CHUNK=5,5个一组)之间的间隔
        delete p[i];
    }

}

int main()
{
    test_static_allocator();

    system("pause");
    return 0;
}
  • 解决前两种缺点:进行包装,避免重复的动作

  • 将分配特定大小区块的memory allocator包装成一个class allocator

    这样每个allocator object都是个分配器,体内维护一个freeStore lists

  • 通过版本的不断迭代,逐渐接近了标准库的写法

4.macro for static allocator

版本四:使用宏定义简化使用

在这里插入图片描述

在这里插入图片描述

5.标准库的allocator之一

在这里插入图片描述

多个链表,对应于多个class

六、new handler

在这里插入图片描述

  • 前面提到,在new expression中,重载了operator new ,并且当malloc() == 0 即内存分配失败时,while loop中调用了_callnewh()函数。

  • 当operator new没有能力分配你所申请的memory时,会抛出异常std::bad_alloc exception,或者有些老版本编译器会返回0。

  • C++编译器会在抛出异常之前(不止一次)调用一个可由client指定的handler,即 new handler(),观察程序员是否对该问题进行解决。

    不止一次调用是指,若new handler没有调用abort()或exit(),上图右边的while loop会一直进行,直到获得足够memory

  • 设计良好的new handler()只有两个选择:

    • 让更多的memory可用
    • 调用abort()或exit()

在这里插入图片描述

#include <iostream>
#include <cassert>

using namespace std;

// 此处为一个 new handler
void no_more_memory()
{
    cerr << "out of memory" << endl;
    abort();
}

int main()
{
    // 该函数由标准库提供
    // typedef void (*new_handler)();
    // new_handler set new_handler(new_handler p) throw();      传入一个new_handler,返回一个new_handler
    // 设计原因,先前有一个new_handler_A,再传入一个new_handler_B,返回以前的new_handler_A以便记录
    set_new_handler(no_more_memory);

    int* p = new int[10000000000];
    assert(p);      // 其作用是如果它的条件返回错误,则终止程序执行

    system("pause");
    return 0;
}

vsCode的测试结果:

在这里插入图片描述

七、=default 与 =delete

在这里插入图片描述

在这里插入图片描述

测试发现:

  • operator new与operator delete 不能为 =default,

    error: ‘static void Foo::operator delete(void*)’ cannot be defaulted

  • 但可以为 =delete,此时的operator new 与 operator delete无法使用

    error: use of deleted function 'static void* Foo::operator new(size_t)

部分代码地址

最后:
欢迎指正不足或错误的地方。如果文章对你有所帮助,欢迎点赞支持。欢迎转载。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值