自定义C++内存管理

目录

1、new、malloc、delete、free

2、new / delete系列的重载

3、自定义动态对象的存储空间 

4、动态内存申请的结果

5、跨编译器统一new的行为

6、静态分配器的实现

7、C++标准分配器的使用

8、分配器的实现原理


1、new、malloc、delete、free

new关键字与malloc函数的区别

       - new关键字是C++的—部分 ,malloc是由C库提供的函数 

       - new以具体类型为单位进行内存分配 ,malloc以字节为单位进行内存分配 

       - new在申请内存空间时可进行初始化malloc仅根据需要申请定量的内存空间 

       - new在所有C++编译器中都被支持 ,malloc在某些系统开发中是不能调用 

       - new能够触发构造函数的调用 ,malloc仅分配需要的内存空间

       - 对象的创建只能使用new ,malloc不适合面向对象开发

delete和free的区别 

       - delete在所有C++编译器中都被支持 ,free在某些系统开发中是不能调用 

       - delete能够触发析构函数的调用 ,free仅归还之前分配的内存空间 

       - 对象的销毁只能使用delete ,free不适合面向对象开发 

2、new / delete系列的重载

new / delete的本质是C++预定义的操作符 ,C++对这两个操作符做了严格的行为定义 

new行为

     ① 调用 operator new 函数

     ② 获取足够大的内存空间(默认为堆空间) 

     ③ 在获取的空间中调用构造函数创建对象 (初始化对象)

delete行为

     ①  调用析构函数

     ②  调用 operator delete 函数 (顺序理解:肯定应该是先调用析构释放持有的其他系统资源,再释放自己本身所占的堆内存)

     ③ 归还对象所占用的空间(默认为堆空间)

动态对象数组创建通过new[]完成 ,动态对象数组的销毁通过delete[]完成 

new[]实际需要返回的内存空间可能比期望的要多 ,对象数组占用的内存中需要保存数组长度信息 (4B)数组长度信息用于确定构造函数和析构函数的调用次数

new[]行为

     ① 调用 operator new[] 函数

     ② 获取足够大的内存空间(默认为堆空间) 

     ③ 在获取的空间中多次调用构造函数创建对象 

delete[]行为

     ① 根据数组长度信息多次调用析构函数

     ② 调用 operator delete[] 函数

     ③ 归还对象所占用的空间(默认为堆空间)

placement new 允许将对象构建在已经分配的内存中(在指定的内存上调用构造函数)

#include <iostream>
using namespace std;

class A
{
public:
    int id;
    void init(){
        A();
    }
    A() : id(0){}
    A(int i) : id(i){}
    ~A(){}
};

int main()
{
    A* buf = new A[3]; // 三次调用构造函数,buf指向第一个A对象
    A* tmp = buf;

    for(int i = 0; i < 3; i++)
    {
        new(tmp++)A(i+100);  // placement new: 在tmp指向的地方调用构造函数
    }

    for(int i = 0; i < 3; i++)
    {
        cout << buf[i].id << endl; // 100,101,102
    }

    // placement new 等价于下面写法
    void* m = operator new(sizeof(A), buf); // void* operator new (size_t, void* loc){ return loc; }
    A* p = static_cast<A*>(m);
    p->init(); // 本意:p->A::A(); 但不支持这种写法

    delete [] buf; // 多次调用析构函数,

    return 0;
}

在C++中能够重载operator new等函数

  • 全局重载(不推荐)  
  • 局部重载(针对具体类进行重载) 
#include <iostream>
#include <malloc.h>
using namespace std;

// static member function, 默认都为静态成员函数
// 重载::operator new
void* operator new (size_t size)
{
    cout << "::operator new " << size << endl;
    return malloc(size);
}
void* operator new[] (size_t size)
{
    cout << "::operator new[] " << size << endl;
    return malloc(size);
}
// 重载::operator delete
void operator delete (void* ptr) throw()
{
    cout << "::operator delete" << endl;
    free(ptr);
}
void operator delete[] (void* ptr) throw()
{
    cout << "::operator delete[]" << endl;
    free(ptr);
    // 个人猜测: 数组长度信息主要用于确定析构函数的调用次数。在malloc看来就是要size大小的内存,而不知这片内存是否是数组,free时也是释放这片内存
    // 所以不要误会数组长度信息是给free释放对象数组的,free释放的size大小的内存是根据malloc采用哪种实现确定的。
}

// 在类中重载operator new, 优先级更高
class A
{
int i;
public:
    //A(){}
    ~A(){}
    void* operator new (size_t size) // 默认就是静态的
    {
        cout << "A::operator new" << endl;
        return malloc(size);
    }
    void operator delete (void* p, size_t)
    {
        cout << "A::operator delete" << endl;
        free(p);
    }
};

// 重载 operator new() 的多个版本,所谓new(ptr)就是标准库重载的, 其中的第一个参数必须是size_t
void* operator new(size_t size, A* p)
{
    cout << "::operator new(p)" << endl;
    (void)size;
    return p;
}
void* operator new(size_t size, long extra)
{
    cout << "::operator new(extra)" << endl;
    return malloc(size + extra);
}

int main()
{
    A* p = new A(); // 编译器转为 void* m = operator new(sizeof(A)); p = static_cast<A*>(m); p->A::A();
    A* p1 = ::new A();
    A* p2 = ::new(p) A();
    A* p3 = ::new(10) A();
    A* arr = new A[2];

    cout << endl;
    cout << "address: " << (void*)arr << endl;
    cout << "len: " << *((int*)arr - 1) << endl;
    cout << endl;

    ::delete p3;
    ::delete p1;
    delete p;     // 编译器行为:p->~A(); operator delete(p);
    delete[] arr; // 如果delete arr; 只会调用一次析构函数 ,delete[] arr会根据cookie多次调用析构函数
    return 0;
}

 

3、自定义动态对象的存储空间 

面试题:new关键字创建出来的对象位于什么地方?

静态存储区中创建动态对象.cpp

#include <iostream>    
  
using namespace std;  
  
class Test  
{  
    static const unsigned int COUNT = 4;  
    static char c_buffer[];  // 静态存储区的一片空间,这里仅声明标识符,类外定义
    static char c_map[];     // 标记对应空间是否使用
      
    int m_value;  
public:  
    void* operator new (unsigned int size)  
    {  
        void* ret = NULL;  
          
        for(int i=0; i<COUNT; i++)  // 在c_buffer中找到空闲位置
        {  
            if( !c_map[i] )  
            {  
                c_map[i] = 1;  
                  
                ret = c_buffer + i * sizeof(Test);  // 可用空间的首地址
                  
                cout << "succeed to allocate memory: " << ret << endl;  
                  
                break;  
            }  
        }  
          
        return ret;  
    }  
      
    void operator delete (void* p)  
    {  
        if( p != NULL )  
        {  
            char* mem = reinterpret_cast<char*>(p);  

            int index = (mem - c_buffer) / sizeof(Test);  
            int flag = (mem - c_buffer) % sizeof(Test);  
             
            if( (flag == 0) && (0 <= index) && (index < COUNT) )  
            {  
                c_map[index] = 0;  
                  
                cout << "succeed to free memory: " << p << endl;  
            }  
        }  
    }  
};  
  
char Test::c_buffer[sizeof(Test) * Test::COUNT] = {0};  
char Test::c_map[Test::COUNT] = {0};  
  
int main(int argc, char *argv[])  
{  
    cout << "===== Test Single Object =====" << endl;  
       
    Test* pt = new Test;  
      
    delete pt;  
      
    cout << "===== Test Object Array =====" << endl;  
      
    Test* pa[5] = {0};  
      
    for(int i=0; i<5; i++)  
    {  
        pa[i] = new Test;  
          
        cout << "pa[" << i << "] = " << pa[i] << endl;  
    }  
      
    for(int i=0; i<5; i++)  
    {  
        cout << "delete " << pa[i] << endl;  
          
        delete pa[i];  
    }  
      
    return 0;  
}

 

面试题 :如何在指定的地址上创建C++对象? 

自定义动态对象的存储空间.cpp

#include <iostream>  
#include <cstdlib>  
  
using namespace std;  
  
class Test  
{  
    static unsigned int c_count;  
    static char* c_buffer;  
    static char* c_map;  
      
    int m_value;  
public:  
    static bool SetMemorySource(char* memory, unsigned int size)  
    {  
        bool ret = false;  
          
        c_count = size / sizeof(Test);  
          
        ret = (c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));  
          
        if( ret )  
        {  
            c_buffer = memory;  
        }  
        else  
        {  
            free(c_map);  
              
            c_map = NULL;  
            c_buffer = NULL;  
            c_count = 0;  
        }  
          
        return ret;  
    }  
      
    void* operator new (unsigned int size)  
    {  
        void* ret = NULL;  
          
        if( c_count > 0 )  
        {  
            for(int i=0; i<c_count; i++)  
            {  
                if( !c_map[i] )  
                {  
                    c_map[i] = 1;  
                      
                    ret = c_buffer + i * sizeof(Test);  
                      
                    cout << "succeed to allocate memory: " << ret << endl;  
                      
                    break;  
                }  
            }  
        }  
        else  
        {  
            ret = malloc(size);  
        }  
          
        return ret;  
    }  
      
    void operator delete (void* p)  
    {  
        if( p != NULL )  
        {  
            if( c_count > 0 )  
            {  
                char* mem = reinterpret_cast<char*>(p);  
                int index = (mem - c_buffer) / sizeof(Test);  
                int flag = (mem - c_buffer) % sizeof(Test);  
                  
                if( (flag == 0) && (0 <= index) && (index < c_count) )  
                {  
                    c_map[index] = 0;  
                      
                    cout << "succeed to free memory: " << p << endl;  
                }  
            }  
            else  
            {  
                free(p);  
            }  
        }  
    }  
};  
  
unsigned int Test::c_count = 0;  
char* Test::c_buffer = NULL;  
char* Test::c_map = NULL;  
  
int main(int argc, char *argv[])  
{  
    char buffer[12] = {0};  
      
    Test::SetMemorySource(buffer, sizeof(buffer));  
      
    cout << "===== Test Single Object =====" << endl;  
       
    Test* pt = new Test;  
      
    delete pt;  
      
    cout << "===== Test Object Array =====" << endl;  
      
    Test* pa[5] = {0};  
      
    for(int i=0; i<5; i++)  
    {  
        pa[i] = new Test;  
          
        cout << "pa[" << i << "] = " << pa[i] << endl;  
    }  
      
    for(int i=0; i<5; i++)  
    {  
        cout << "delete " << pa[i] << endl;  
          
        delete pa[i];  
    }  
      
    return 0;  
}  

 

 

4、动态内存申请的结果

malloc函数申请失败时返回NULL 

new关键字申请失败时(根据编译器的不同) 

  • 返回NULL值 (古老C++编译器兼容malloc)
  • 抛出std::bad_alloc异常 (现代C++编译器)

编译器的不同导致new内存申请失败时的行为不同,那么new语句中的异常是如何抛出的?

new 关键字在C++规范中的标准行为(但编译器不一定遵循)

在堆空间申请足够大的内存 

  • 成功: 在获取的空间中调用构造函数创建对象 ,返回对象的地址 
  • 失败: 抛出std::bad_alloc异常

new在分配内存不足时 (上面的失败情况下)

  • 如果空间不足,会调用全局的new_handler()函数 
  • new_handler()函数中抛出std::bad_alloc异常 

可以自定义new _handler()函数 

  • 处理默认的new内存分配失败的情况 
typedef void  (*new_handler)();

new_handler set_new_handler (new_handler new_p) throw(); // C++98
new_handler set_new_handler (new_handler new_p) noexcept; // C++11

new_handler()的定义和使用 

void my_new_handler()
{
    cout << "No enough memory" << endl;

    exit(1);
}

int main()
{
    set_new_handler(my_new_handler); 

    // ...

    return 0;
}

5、跨编译器统一new的行为

问题 :如何跨编译器统一new的行为, 提高代码移植性? 

解决方案 (3种)

  -全局范围(不推荐) 

         ☛ 重新定义new / delete的实现,不抛出任何异常

         ☛ 自定义new_handler()函数,不抛出任何异常 

  -类层次范围 

         ☛ 重载new / delete , 不抛出任何异常 

  -单次动态内存分配 

         ☛ 使用nothrow参数,指明new不抛出异常 

#include <iostream>
#include <new>
#include <cstdlib>
#include <exception>

using namespace std;

class Test
{
    int m_value;
public:
    Test()
    {
        cout << "Test()" << endl;
        
        m_value = 0;
    }
    
    ~Test()
    {
        cout << "~Test()" << endl;  
    }
    
    void* operator new (unsigned int size) throw()
    {
        cout << "operator new: " << size << endl;
        
        // return malloc(size);
        
        return NULL;  // 内存申请失败
    }
    
    void operator delete (void* p)
    {
        cout << "operator delete: " << p << endl;
        
        free(p);
    }
    
    void* operator new[] (unsigned int size) throw()
    {
        cout << "operator new[]: " << size << endl;
        
        // return malloc(size);
        
        return NULL;
    }
    
    void operator delete[] (void* p)
    {
        cout << "operator delete[]: " << p << endl;
        
        free(p);
    }
};

void my_new_handler()
{
    cout << "void my_new_handler()" << endl;
}

void ex_func_1()
{
    new_handler func = set_new_handler(my_new_handler); // 返回原来的new_handler处理函数
    
    try
    {
        cout << "func = " << func << endl;   // 若默认没有处理函数func为NULL
        
        if( func )
        {
            func(); // 若默认情况有处理函数,会抛出bad_alloc异常
        }
    }
    catch(const bad_alloc&)
    {
        cout << "catch(const bad_alloc&)" << endl;
    }
}

void ex_func_2()
{
    Test* pt = new Test();
    
    cout << "pt = " << pt << endl;
    
    delete pt;
    
    pt = new Test[5];
    
    cout << "pt = " << pt << endl;
    
    delete[] pt; 
}

void ex_func_3()
{
    int* p = new(nothrow) int[10];  // 不管成功与否都不抛出异常,失败返回NULL
    
    // ... ...
    
    delete[] p; 
    
    int bb[2] = {0};
    
    struct ST
    {
        int x;
        int y;
    };
    
    ST* pt = new(bb) ST(); // 将ST的对象创建到bb栈空间里
    
    pt->x = 1;
    pt->y = 2;
    
    cout << bb[0] << endl;
    cout << bb[1] << endl;
    
    pt->~ST();  // 需要手工调用析构函数
}

int main(int argc, char *argv[])
{
    // ex_func_1();
    // ex_func_2();
    // ex_func_3();
    
    return 0;
}

① 调用ex_fun_1()函数

VS2015和g++打印结果都为0,说明默认没有全局的new_handler处理函数

遵循标准C++的BCC编译器有默认new_handler处理函数,在这个函数中会抛出bad_alloc异常

② 调用ex_fun_2()函数

在重载new时若不加上throw()

    - 在g++下,new的重载返回NULL时,在NULL地址处调用了构造函数创建对象,操作0地址,段错误

    - 在VS2015和BCC下,new的重载返回NULL时,没有调用构造函数,pt打印0

三款编译器动态内存申请失败时的行为不一致,加上throw统一了编译器行为,在new失败时不抛任何异常,pt都打印0

③ 调用ex_fun_3()函数

实验结论 

-不是所有的编译器都遵循C++的标准规范 

-编译器可能重定义new的实现,并在实现中抛出bad_alloc异常(虽然现代编译器申请失败时会抛出bad_alloc异常,但不一定是在new_handler里抛出的 ,实验可以看到VS和g++默认没有设置全局的new_handler函数,只有BCC设置了)

-编译器的默认实现中,可能没有设置全局的new_handler()函数

-对于移植性要求较高的代码,需要考虑new的具体细节 

 

查看分析VS2010下new的实现文件   

xxx\Microsoft Visual Studio 10.0\VC\crt\src\new.cpp

/***
*new.cxx - defines C++ new routine
*
*       Copyright (c) Microsoft Corporation.  All rights reserved.
*
*Purpose:
*       Defines C++ new routine.
*
*******************************************************************************/


#ifdef _SYSCRT             /* 通过该宏区分两个版本的new实现 */
#include <cruntime.h>
#include <crtdbg.h>
#include <malloc.h>
#include <new.h>
#include <stdlib.h>
#include <winheap.h>
#include <rtcsup.h>
#include <internal.h>

void * operator new( size_t cb )
{
    void *res;

    for (;;) {

        //  allocate memory block
        res = _heap_alloc(cb);   

        //  if successful allocation, return pointer to memory

        if (res)
            break;

        //  call installed new handler 申请失败
        if (!_callnewh(cb))
            break;

        //  new handler was successful -- try to allocate again
    }

    RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0));  // 调试代码忽视

    return res;
}
#else  /* _SYSCRT */

#include <cstdlib>
#include <new>

_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                static const std::bad_alloc nomem;   // 这个实现中只要不成功必然抛异常
                _RAISE(nomem);
                }

        return (p);
        }

/*
 * Copyright (c) 1992-2002 by P.J. Plauger.  ALL RIGHTS RESERVED.
 * Consult your license regarding permissions and restrictions.
 V3.13:0009 */
#endif  /* _SYSCRT */

_callnewh

如果没有设置全局的处理函数或者设置了全局的处理函数但这个处理函数不能使有更多堆空间,返回值为0 

如果没有设置全局处理函数就抛出bad_alloc异常

 

6、静态分配器的实现

之前的代码中,每一次new都会调用一次malloc,malloc的实现方式多种多样,速度有快有慢,所以可以自己通过一次申请大量内存,划分管理,减少malloc的调用的次数

而且malloc每次分配内存可能需要分配一些额外的内存来记录分配的内存的大小信息等,这就势必会浪费很多内存(malloc很多小块每一个小块都会浪费一份,malloc一个大块,只浪费一份)

#include <iostream>

class allocator
{
private:
    struct Node
    {
        struct Node* next;
    };
    Node* freeStore = nullptr; // 空闲块链表头指针
    const int CHUNK = 5;        // 5个大块
public:
    void* allocate(size_t size)
    {
        Node* p = nullptr;

        if(freeStore == nullptr)
        {
            size_t chunk = CHUNK * size;  // 当没有空闲块了提前申请需求内存的5倍,即5个大块

            std::cout << "allocate: " << chunk << std::endl;
            freeStore = p = (Node*)malloc(chunk); // 分配一大块内存

            for(int i = 0; i < CHUNK-1; i++)
            {
                p->next = (Node*)((char*)p + size); // 每个大块的前四个字节指向下一个大块
                p = p->next;
            }
            p->next = nullptr;
        }

        p = freeStore; // 返回空闲块的头指针
        freeStore = freeStore->next;

        return p;
    }
    void deallocate(void* p, size_t)
    {
        ((Node*)p)->next = freeStore; // 插入空闲块链表头部
        freeStore = (Node*)p;
    }
};

// 当调用operator new 时一次会分配5个连续A大块,当再次需要时直接使用这5个大块,当使用完了再次申请5个大块
#define DECLARE_POOL_ALLOC()\
public:\
    void* operator new (size_t size) { return myAlloc.allocate(size); }\
    void operator delete (void* p, size_t size) { return myAlloc.deallocate(p, size); }\
protected:\
    static allocator myAlloc;

#define IMPLEMENT_POOL_ALLOC(class_name)\
allocator class_name::myAlloc;

class A
{
    int a[4];
public:
    DECLARE_POOL_ALLOC()
};

IMPLEMENT_POOL_ALLOC(A)

int main()
{
    A* arr[10];

    std::cout << "sizeof (arr) = " << sizeof (arr) << std::endl;

    for(int i = 0; i < 10; i++)
    {
        arr[i] = new A();
        std::cout << arr[i] << std::endl;
    }

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

    return 0;
}

7、C++标准分配器的使用

分配器都是给容器用的,自己也可以使用,但需要记录大小以便归还,容器的元素大小固定

VC6 和 BC5 和 G2.9 和 G4.9  VS2013的 std::allocator(标准分配器) 都只是以 ::operator new 和 ::operator delete 实现 allocate() 和 deallocate(),没有任何的特殊设计

在 G2.9 中容器的默认分配器实际使用的不是 std::allocator 而是 std::alloc,

而 G2.9的 std::alloc 到4.9 变成了 __gnu_cxx::__pool_alloc (G4.9标准库有很多扩充的分配器, __pool_alloc就是 G2.9 alloc的化身)

std::allocator:每次分配内存都会调用malloc,内存开销可能增大

__gnu_cxx::__pool_alloc:一次分配很多内存,自己进行管理,减少了malloc的调用次数

#include <iostream>
#include <malloc.h>
#include <ext/pool_allocator.h>
#include <memory.h>
#include <vector>
using namespace std;

int main()
{
    void* p1 = malloc(512);
    free(p1);

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

    char* p2 = new char[2]; // 内部调用malloc
    delete[] p2;

    // 使用分配器,检测编译器
#ifdef _MSC_VER
    int* p4 = allocator<int>().allocate(3, (int*)0); // 分配3个int,  non-static function
    allocator<int>().deallocate(p4, 3);              // 回收3个int
#endif
#ifdef _BORLANDC_
    int* p4 = allocator<int>().allocate(5); // 分配5个int
    allocator<int>().deallocate(p4, 5);     // 回收5个int
#endif

// gunc 2.9
//#ifdef __GNUC__
//    int* p4 = alloc::allocate(512); // 分配512Bytes, static function
//    alloc::deallocate(p4, 512);
//#endif

// gunc 4.9
#ifdef __GNUC__
    int* p4 = allocator<int>().allocate(7); // 分配7个int
    cout << p4 << endl;
    allocator<int>().deallocate((int*)p4, 7);

    void* p5 = __gnu_cxx::__pool_alloc<int>().allocate(9);
    cout << p5 << endl;
    __gnu_cxx::__pool_alloc<int>().deallocate((int*)p5, 9);

    // 容器使用分配器
    vector<int, __gnu_cxx::__pool_alloc<int> > vec;
    vec.push_back(1);
    vec.push_back(2);
    cout <<vec.front() << endl;
    cout <<vec.back() << endl;

#endif

    return 0;
}

8、分配器的实现原理

标准分配器的简单实现

#include <iostream>
#include <malloc.h>

// 标准分配器,需要指明分配元素的类型.G4.9的做法类似
template <typename T>
class Allocator
{
    void* operator new (size_t size) throw()
    {
        return malloc((size));
    }
    void operator delete (void* p)
    {
        free(p);
    }
public:
    // N为分配T类型的个数
    void* allocate(size_t N, const void* = NULL)
    {
        return operator new(N * sizeof(T));
    }
    void deallocate(void* p, size_t)
    {
        ::operator delete (p);
    }
};

int main()
{
    Allocator<int> alloc;

    void* p = alloc.allocate(10);

    *(int*)p = 1;

    std::cout <<  *(int*)p << std::endl;

    alloc.deallocate(p, 10);

    return 0;
}

__gnu_cxx::__pool_alloc实现原理

#include <iostream>
#include <new>
#include <malloc.h>

// 一级分配器: 直接malloc, free
class MallocAlloc
{
public:
    static void* allocate(size_t size)
    {
        void* p;
        while((p = malloc(size)) == 0)
        {
            //typedef void  (*new_handler)();
            std::new_handler old_func = std::set_new_handler(NULL); 

            if( old_func )
            {
                std::set_new_handler(old_func);
                old_func(); // 若默认情况有处理函数,调用试图获得更多可分配空间,可能会抛出bad_alloc异常
            }
            else
            {
                return p; //没有全局处理函数返回0
            }
        }
        return p;
    }
    static void deallocate(void* p, size_t n)
    {
        (void)n; // 和标准分配器的行为不一样
        free(p);
    }
};
// 二级分配器
class PoolAlloc
{
private:
    enum { _ALIGN = 8};         // 16 根链表, 管理的内存大小依次是 8B、16B、24B、...、128B
    enum { _MAX_BYTES = 128};   // 申请的内存超过12Bytes就用一级分配器
    enum { _LIST_NUMS = _MAX_BYTES / _ALIGN };  // 链表的个数:16

    struct Node
    {
        Node* next;
    };

    static Node* free_list[_LIST_NUMS]; // 指针数组, 每个成员都指向一条链表管理内存
private:

    // bytes调高至8的倍数
    static size_t ROUND_UP(size_t bytes)
    {
        return ((bytes) + _ALIGN - 1) & ~(_ALIGN - 1);
        // _ALIGN - 1 -> 00001000 - 1 = 00000111 (假设数据宽度8bit, _ALIGN的数据宽度应该和bytes一致,所以不必计较)
        // ~(_ALIGN - 1) -> 11111000
        // 假设 bytes为8的倍数,那么低3位一定为0,表现为 xxxxx000 即原式转化为  (xxxxx000 + 00000111) & 11111000 -> 最终结果一定为xxxxx000还是8的倍数bytes自己
        // 假设 bytes不为8的倍数,那么肯定可以拆分为8的倍数 + 一个数值o, o属于[1,7]
        // 相当于 bytes - o + (((o) + _ALIGN - 1) & ~(_ALIGN - 1));  -> bytes - o + ((o + 00000111) & 11111000)
        // (o + 00000111) & 11111000, 由于 o属于[1,7], o+7属于[8, 14], 与上11111000, 结果为8
        // 最终结果使bytes调高到了8的倍数
    }

    // 返回bytes大小的内存应该在哪个链表
    static size_t FREELIST_INDEX(size_t bytes)
    {
        return (bytes + _ALIGN - 1) / _ALIGN - 1;
        // bytes是_ALIGN的倍数,(bytes + _ALIGN - 1) / _ALIGN 可以得到一个倍数值 + (ALIGN - 1 / _ALIGN), 倍数值-1就是下标了
        // bytes不是_ALIGN的倍数, 加上(_ALIGN - 1)又是_ALIGN的倍数了
    }

    static char* start_free; // 指向pool头,低地址
    static char* end_free;   // 指向pool尾,高地址
    static size_t heap_size; // 累计申请量
public:
    static void* allocate(size_t size)
    {
        Node** my_free_list;  // 指向链表
        Node* ret;

        if(size > (size_t)_MAX_BYTES)
        {
            //申请的内存超过128B用一级分配器
            return MallocAlloc::allocate(size);
        }

        my_free_list = free_list + FREELIST_INDEX(size); // 应该在哪个链表

        ret = *my_free_list; // 对应链表的头指针

        if(ret == 0) // 当链表还没有管理的结点时 "充值"
        {
            void * r = refill(ROUND_UP(size));
            return r;
        }

        *my_free_list = ret->next; // 指向下一个结点,代表当前结点已被使用

        return ret;
    }

    static void deallocate(void *p, size_t size)
    {
        Node* q = (Node*) p;
        Node** my_free_list;

        if(size > (size_t)_MAX_BYTES)
        {
            MallocAlloc::deallocate(p, size); // 也就是说只回收不是链表管理的内存
            return ;
        }
        my_free_list = free_list + FREELIST_INDEX(size);
        q->next = *my_free_list; // 头插,代表当前结点空闲了,可用其它分配了
        *my_free_list = q;
    }
    //    static void * reallocate(void * p, size_t old_sz,size_t new_sz);

    //}

private:
    // 链表无可用结点,进行充值操作
    static void* refill(size_t n)
    {
        int N = 20;
        char* chunk = chunk_alloc(n, N); // N为传入传出

        if (N == 1) return chunk;  // 只获得一个区块,直接返回

        Node* ret = (Node*)chunk;  // 要返回的区块
        Node** my_free_list = free_list + FREELIST_INDEX(n);
        Node* next_obj = (Node*)(chunk + n); // 下一个区块
        *my_free_list = next_obj; // 将下一个区块放到指针数组中

        for(int i = 1; ; i++)
        {
            Node* current_obj = next_obj;
            next_obj = (Node*)((char*)next_obj + n);
            if(N - 1 == i) // 最后一个区块了
            {
                current_obj->next = NULL;
                break;
            }
            else
            {
                current_obj->next = next_obj;
            }
        }

        return ret;
    }

    // size为一个区块大小,N为区块数(20),N为传入传出参数
    // 获得N个区块大小的内存,返回头指针
    static char* chunk_alloc(size_t size, int& N)
    {
        char* ret = NULL;
        size_t requestSize = size * N;       // 需要申请的字节数
        size_t pool = end_free - start_free; // 当前pool大小


        if(pool >= requestSize) // pool能满足20个区块,取出20个区块
        {
            ret = start_free;
            start_free += requestSize;
            return ret;
        }
        else if(pool >= size) // pool不能满足20个区块
        {
            N = pool / size;  // 看满足几个区块, pool就减少几个区块
            requestSize = size * N;
            ret = start_free;
            start_free += requestSize;
            return ret;
        }
        else // pool若连一个区块的满足不了
        {
            if(pool > 0)// 剩余大小当作碎片处理, 放到对应链表, pool大小为空
            {
                Node** my_free_list = free_list + FREELIST_INDEX(pool);
                ((Node*)start_free)->next  = *my_free_list; // 头插
                *my_free_list = (Node*)start_free;
            }

            // 计算应该申请大小
            size_t toGetBytes = 2 * requestSize + ROUND_UP(heap_size >> 4);

            start_free = (char*)malloc(toGetBytes); // 向系统申请内存

            if(start_free == 0) // 申请失败
            {
                // 向指针数组size大小的右边找,如果找到有可用区块的链表,就只释出第一块给pool, 再次递归调用,
                for(int i = size; i <= _MAX_BYTES; i += _ALIGN)
                {
                    Node** my_free_list = free_list + FREELIST_INDEX(i);
                    Node* head = *my_free_list;
                    if(head != 0)
                    {
                        *my_free_list = head->next;
                        start_free = (char*)head;
                        end_free = start_free + i; // 此时一块的大小为i
                        return chunk_alloc(size, N);
                    }
                }
                // 如果执行到这里说明山穷水尽了, 尝试使用一级分配器的全局处理函数能否获得更多可用空间,若不能也会返回NULL或抛出异常
                end_free = NULL;
                start_free = (char*)MallocAlloc::allocate(toGetBytes);
                if(start_free == NULL){
                    toGetBytes = 0;
                }
            }

            heap_size += toGetBytes;
            end_free = start_free + toGetBytes; // 此时pool有空间了,或者没有,再调用一次

            return chunk_alloc(size, N);
        }
    }
};

PoolAlloc::Node* PoolAlloc::free_list[16] = {0};
char* PoolAlloc::start_free = NULL;
char* PoolAlloc::end_free = NULL;
size_t PoolAlloc::heap_size = 0;

int main()
{
    PoolAlloc alloc;

    void* p = alloc.allocate(10);

    *(int*)p = 1;

    std::cout <<  *(int*)p << std::endl;

    alloc.deallocate(p, 10);

    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值