内存01

上古程序员

640K

  • 在 <font color=red>90年代以前</font>, 操作系统被 <font color=red>M-DOS</font>统治, 在当时的硬件环境下OS只能看到 <font color=red>1MB</font>的内存, 但对于程序员来说, 能利用的空间只有 <font color=red>前面的640kb</font>, 虽然可用的内存很少, 但也涌现了很多优秀的软件


锱铢必较

  • 所以当时的程序员对内存的重视计较到1个字节, 随着工业技术和软件技术的进步, 如今的内存已经是海量的,这就出现一个新的问题--还有必要管理内存吗?


内存管理的层面

  • 严格来说, 内存被os管理, 而且已经管理的很严谨高效, 作为上层的程序员了解内存的管理流程对自己的技术会有一个巨大的提升


memory primitives

内存的层次

20.png
从图中可以看到, c++程序员处于的位置是最上层的 applications

用的最多的是new, new[]
    如果用容器, 则内存基本不用管理

其次也可以调用malloc

至于最底层的os的api, 没有可移植性


它之间的调用关系如上图的箭头所示


函数属性

分配 释放 所属 重载
malloc free c函数 no
new delete c++表达式(==new操作符==) no
::operator new() ::operator delete() c++函数 ==yes==
allocator<T>::allocate() allocator<T>::deallocate() STL分配器 可以自己设计搭配容器
auto p_1 = malloc(sizeof(int));
free(p_1);

stringstream* p_b = new stringstream();
delete p_b;

void* p_c = ::operator new(sizeof(int));
::operator delete(p_c);


/** 
    STL的分配器必须创建对象调用成员方法
    但实际管理的内存是统一的
    所以 allocate和deallocate时候, 虽然对象不是同一个
    但没有问题

    而且 allocate的时候, 第2个参数(VC6中必传)void*可以不传
    deallocate的时候必须告诉分配器还什么指针(p_d)以及还多大(1)
        第2个参数要和allocate时第1个参数一致
*/
int* p_d = allocator<int>().allocate(1); //1个int
allocator<int>().deallocate(p_d,1);

new的初步探究

new的工作流程

  • c++的程序员基本都会用 <font color=red>new</font>来为对象分配一个 <font color=red>堆内存</font>, 并且 <font color=red>new会调用对应的构造函数</font>, 构造函数是用来初始化对象的, 所以总结出new的功能是:
    1. 在堆中分配一块内存
    2. 调用对应的构造函数
调用形式:
    Object* obj = new Object(v1, v2)



new实际的工作(伪代码):
    try{
        void* obj = operator::new(sizeof(Object));

        Object* obj_p = static_cast<Object*>(obj);

        obj_p->Object(v1,v2); // 只有编译器可以这样调用(但vc中可以)

    }cache(std::bad_alloc){
        // 分配内存失败
    }


根据上面的内存函数的调用流程, 所以 operator::new会调用malloc, 下面用MSVC测试一下:


测试new的调用流程

21.png

22.png

23.png

24.png

struct A{
    int a;
    int b;
    A(){}
    A(int _a, int _b): a(_a), b(_b){}
};
int main(int arg, char** args){
    A* tmp_a = new A(2, 8);
    delete tmp_a;
}

/** 
    上面的4张图是在MSVC中反汇编的运行时代码

    从第1张图可以看出, new 调用了operator new

    从第3张图可以看出, operator new内部调用了malloc

    operator new的函数从VC的汇编大致可以得出长这样:
        void* __CRTDECL operator new(size_t const size){
            for(;;){
                if(void* const block = malloc(size))
                    ...
            }
        }


    ps: 实际上编译器是在 new的地方调用了对应的构造函数, 并不是在new的内部
        new只是编译器识别的一个标识符, 并不是函数, 编译器看到new后会malloc, 然后调用构造函数


    VS2019可以看到 operator new的源码:
*/
_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
    for (;;)
    {
        if (void* const block = malloc(size))
        {
            return block;
        }

        if (_callnewh(size) == 0)
        {
            if (size == SIZE_MAX)
            {
                __scrt_throw_std_bad_array_new_length();
            }
            else
            {
                __scrt_throw_std_bad_alloc();
            }
        }

        // The new handler was successful; try to allocate again...
    }
}
/** 
    上面的 operator new的作用是 调用malloc分配内存
    
    当malloc成功后直接返回
    当malloc失败后:
        并不会再次 malloc
        而是调用 _callnewh() new_handler()
            这个函数的作用是 向自己定义的函数索取内存
            所以 new_handler可以理解为释放一些缓存
        调用完new_handler后, 可能释放了内存, 这个时候再尝试调用malloc获取内存
    
*/

delete的初步探究

工作流程

25.png

26.png

27.png

28.png

29.png
using namespace std;
struct A {
    int a;
    int b;
    A() {}
    A(int _a, int _b) : a(_a), b(_b) {}
    ~A() {}
};
int main(int arg, char** args) {
    A* tmp_a = new A(2, 8);
    delete tmp_a;
}


析构的另一种调用方式

struct A {
    int a;
    int b;
    A(int _a, int _b) : a(_a), b(_b) {}
};

int main(int arg, char** args) {
    A* tmp_a = new A(2, 8);

    /** 
        直接利用指针调用A的析构函数, 标准规格是允许的
            在 19_stl.md中的 分类对算法的影响--case3 中也这样用到过
        
    */
    tmp_a->~A();


    /** 
        虽然调用了析构函数, 但事实上tmp_a的空间并没有释放 
        所以可以访问tmp_a

        ps: 如果是delete tmp_a; 就不能会访问
    */
    cout << tmp_a->a << endl;


    /** 
        在A中也没有定义析构函数函数, 在上小节的 工作流程中的
        最后1张图提出了1个问题, 如果没有手动写出析构函数, 那编译器
        会不会生成默认的析构函数, 但这里测试的编译器至少在调用时生成了

        在以前学习类的时候, 说过1个类定义了如果没有在任何地方用到就不会
        生成默认的几个成员函数, 这里至少提高到不显示调用就不会生成
    */
}


array new/delete

语法

struct A {
    int a;
    int b;
    A() {
        cout << "A() : this:" << this << endl;
    }
    A(int _a, int _b) : a(_a), b(_b) {
        cout << "A(int,int): this: " << this << endl;
    }
    ~A() {
        cout << "~A(): this: " << this << endl;
    }
};


int main(int arg, char** args){
    //A* tmp = new A[2](2,6);       //_code_a

    A* tmp = new A[2];              //_code_b

    //delete tmp;                   //_code_c
    
    delete[] tmp;                   //_code_d

    cout << "over\n";               //_code_e
}

/** 
    _code_a 本身想调用A的有参构造, 但语法不成立

    _code_b 用了array new, 内部会调用malloc分配2个A大小的空间
            然后调用2次A默认无参的ctor

    _code_c
        cl.exe(MSVC的c++编译器, 这里用的是2019)编译运行后runtime error, 但会调用1次tmp[0]的析构函数
        g++编译运行后, 没有问题, main函数正常结束, 也只会调用1次tmp[0]的析构函数




    _code_d会调用2次析构函数, 是正确的合理的写法
        先调用tmp[1], 再调用tmp[0]

        new A[2] 内部会调用malloc分配2个A大小的空间, 返回的是 A*
        new A    内部会调用malloc分配1个A大小的空间, 返回的是 A*

        但最后用 delete tmp搭配new A[2] 时出现错误, 说明new A和new A[]
        的过程是不一样的, 至少结构是不一样的, 这留到后面的malloc再说



    对于_code_c, 在g++的环境下, 可以通过, 但并不适合所有的类
    当1个类的成员变量有指针时, _code_c搭配_code_b, 就会有内存泄露
    因为数组在释放时, 并没有调用所有元素的析构函数
    并且, 这里面有1个结构上的问题, 后面再说
    
   ps: 如果A没有手动写出析构函数, _code_c这一行在VC编译后运行没有问题, 但同样可能有内存泄露

*/


placement new

概念

  • 标准不允许直接用指针调用构造函数, 但 <font color=red>给出了另一种调用语法</font>, 可以 <font color=red>在现有的对象的空间中调用构造函数</font>, 它不会创建新的空间
struct A {
    int a;
    int b;
    A() {
        cout << "A() : this:" << this << endl;
    }
    A(int _a, int _b) : a(_a), b(_b) {
        cout << "A(int,int): this: " << this << endl;
    }
    ~A() {
        cout << "~A(): this: " << this << endl;
    }
};

int main(){
    A* tmp = new A[2];

    auto _idx = tmp;

    for(int i = -1; ++i < 2;){
        /** 
            利用placement new的语法, 手动调用A的构造函数
            但不会创建新对象, 就是说一个一个遍历数组, 
            通过指针访问对应元素的构造函数

            下一节的 operator new第2个参数 会说明 placement new的实现机制
        */
        new(_idx++)A(2,75);
    }

    delete[] tmp;
}


operator new第2个参数

int main(int arg, char** args) {
    A* a = new A();
    auto result = operator new(sizeof(A), a);

    cout << a << endl;
    cout << result << endl;
    
     
    //a和result的地址一样, 其实这里可以找到operator new的源码
}
#define __PLACEMENT_NEW_INLINE
_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) _Post_satisfies_(return == _Where)
inline void* __CRTDECL operator new(size_t _Size, _Writable_bytes_(_Size) void* _Where) noexcept
{
    (void)_Size;
    return _Where;  //如果有第2个参数则直接返回第2个参数
}


探究operator new

int main(int arg, char** args) {
    A* a = new A();
    new(a)A(27, 224);

    void* b = ::operator new(sizeof(A));
    cout << b << endl;  

    cout << a->a << endl;
    cout << a->b << endl;
}

/**
    对于这样的调用, 汇编代码如下:
*/
30.png

31.png

33.png

34.png

重载内存函数

内存分配的过程

35.png
从图中可以看出, 当出现
    new Foo(x)
后, 编译器会查看 Foo 这个类有没有 实现 operator new(size_t)的static函数
如果有就会调用Foo这个版本的operator new, 如果没有就调用全局的::operator new
delete也是如此

所以可以为一个类单独实现operator new和operator delete, 也可以重载全局的operator new/delete
但很少这么做, 因为全局的版本是照顾所有的类


重载::operator new/delete

  • 上面说了, 重载 <font color=red>全局的operator new/delete</font>影响深远, 但可以重载, 方法是在 <font color=red>另1个==namespace==中声明和全局版本相同的函数签名</font>
using namespace std;
struct A {
    int a;
    int b;
    A() {
        cout << "A() : this:" << this << endl;
    }
    A(int _a, int _b) : a(_a), b(_b) {
        cout << "A(int,int): this: " << this << endl;
    }
    ~A() {
        cout << "~A(): this: " << this << endl;
    }
};

////////////////////////////
////// 以下4个函数就在当前的文件中声明定义

inline void* operator new(size_t len) {
    return malloc(len); 
}

inline void* operator new[](size_t len) {
    return malloc(len);
}

inline void operator delete(void* ptr) {
    return free(ptr);
}

inline void operator delete[](void* ptr) {
    return free(ptr);
}
int main(int arg, char** args) {
    A* a = new A();  //调用到上面自定义的operator new

    void* b = ::operator new(sizeof(A));  // 虽然指定的是::operator new, 但还是调用到上面自定义的版本

    // 对于a是正确的释放, 先调用a的析构函数, 再调用上面的operator delete
    delete a;

    // 对于b, 释放的操作应该显示调用operator delete
    /// 这里用delete后, 并没有调用A的析构函数, 而是直接调用了 operator delete
    ///// 可见编译器对于 delete void* 的时候, 是直接转换为 opreator delete
    delete b;
}


重载类本身的operator new/delete

using namespace std;
struct A {
    A() {
        cout << "A() : this:" << this << endl;
    }
    ~A() {
        cout << "~A(): this: " << this << endl;
    }


    /** 
        这个函数写不写static都会被编译器做成static
    */
    static void* operator new(size_t len) {
        return malloc(len);
    }

    static void operator delete(void* ptr, size_t len) {
        return free(ptr);
    }
};

int main(int arg, char** args) {
    //调用A::operator new版本
    A* a = new A();
    
    // 调用全局的operator new, 如果想调用A的版本, 则显示调用 A::operator new()
    void* b = operator new(sizeof(A));
    
    
    A* c = ::new A();       //调用全局的operator new
    void* d = ::new A();    //调用全局的operator new
    
    delete a;   //先调用a的析构函数,再调用a的operator delete
    delete b;   //直接调用了全局的operator delete
    ::delete c;
    ::delete d;
}


本质就是重载new

  • new是 <font color=red>关键字</font>, 编译器看到new, 会转换成 <font color=red>operator new</font>的调用, 所以new也可以理解为操作符(==毕竟用operator修饰的==)
A* a = new A();   //转换成 A* a = ::operator new(sizeof(A));
                  // 如果有A自己定义了 operator new, 则会转换成 A* a = operator new(sizeof(A));
                  // 接着调用A的无参ctor
                  /// 也就是说这句代码完整的应该是这样 A* a = new(sizeof(A)) A();
                  /////// 但编译器不允许这么写


/** 
    前面在说placement new的时候:
        new(a)A(2,2);  //A(2,2)表示A有一个2参数的ctor, new(a)中的a是一个void*
    本质是调用了 operator new(size_t, void*)的函数, 那就说明:
        可以为 operator new 重载不同参数的operator new函数
*/
struct A {
    A() {
        cout << "A() : this:" << this << endl;
    }
    ~A() {
        cout << "~A(): this: " << this << endl;
    }

    // 最原始的operator new
    void* operator new(size_t len) {
        return malloc(len);
    }
    
    // 就是placement new(前面说过placement new不会创建新的内存)
    void* operator new(size_t len, void* t) {
        return t;
    }

    // 为operator new重载自己的版本
    static void* operator new(size_t len, const char* str) {
        void* mem = malloc(len);
        cout << "str" << endl;
        return mem;
    }

    static void operator delete(void* ptr, size_t len) {
        return free(ptr);
    }
};

int main(int arg, char** args) {
    // 调用operator new(size_t)
    A* a = new A; //或 new A()
    
    // 调用 operator new(size_t, void*)
    ///很可能会出问题, 因为*args并不是A*的指针
    /// 所以调用ctor时,如果ctor内部访问了成员变量, 就会有问题
    A* b = new((void*)*args) A; // 无参的ctor可以不用A()

    // 调用operator new(size_t, const char*)
    A* c = new("hello") A;  // 无参的ctor 可以不用 A()

    
    /** 
        重载operator new时, 第1个参数必须是size_t, 并且用new 调用时不能传递第1个参数
    */
}


关于多参new对应的delete

  • 上面重载了参数不一的new(==operator new==), 那是不是要重载对应的delete呢?


  • 理论上delete的工作 <font color=red>只是为了释放内存</font>, 所以只需要一个==指针==就可以了, 但c++在语法上支持 <font color=red>多参的delete重载</font>, 但delete的调用格式只有 ==delete type==, 所以关于重载的delete版本什么时候被调用, 并不是用户手动调用, 而是对应版本的==ctor发生异常后, 会由crt来调用对应的delete
struct A{
    A():{}                                          //_ctor_a
    A(int):{throw 2;}                               //_ctor_b

    static operator new(size_t len){ .. }           //_new_a
    static operator new(size_t len, int e){ .. }    //_new_b
    static operator delet(void* ptr){ ... }         //_del_a
    static operator delet(void* ptr, int e){ ...}   //_del_b
};

int main(int arg, char** args){
   A* a = new A();      //_new_a, _ctor_a
   
   A* b = new(444)A;    //_new_b, _ctor_a

   A* c = new A(2);     //_new_a, _ctor_b
                        //_ctor_b有异常但却没有调用 _del_a

   A* d = new(42)A(75); //_new_b, _ctor_b
                        //有异常, 但却没有调用_del_b

   /** 
        也就是说重载的operator delete就是为了处理new的异常, 但上面却没有调用
        在g++早期的版本, 上面是会调用对应的 _del_a和_del_b, 现在不会了, VC也不会


        事实上, new的异常处理有专门的语法格式
   */
}
class A {
public:
    A() try:a(test()) {
        cout << "A()\n";

    }catch (...) {
        // 这里可以访问当前对象的成员, 所以在这里释放
        if(b){
            delete b;
            b = nullptr;
        }
        cout << "A catch ...\n";
    }
    int test() {
        b = new int();
        *b = 64;
        throw 2;

        return 4;
    }
    ~A() {
        if(b){
            delete b;
            b = nullptr;
        }
        cout << "A destructor\n";
    }

private:
    int a;
    int* b;
};



int main(int arg, char** args) {
    A a;
    return 0;
}

利用operator new实现类似java中的satic方法

  • 在java中, 一个类最先调用的函数是 static方法(==不属于对象==),c++中原本是没有的, 但利用operator new可以模拟出类似的效果
public class A{
   static{
        System.out.println("fir");
    }

    static public void main(String[] args){
        System.out.println("two");
    }
}
/** 
    上面最先调用的是 static{}, 下面是c++的实现
*/
struct A{
    static void do_sth{
        ...
    }
    static operator new(size_t len){
        // do something
        static once_flag o;
        std::call_once(once_flag, do_sth);
        return ::operator new(len);
    }
};
/** 
    其实上述的实现和java中的static静态函数并不等价,
    java中的static一定在main前面调用, 并且不存在线程竞争
    而cpp的实现, 是程序已经运行起来, 并且可能存在线程竞争
*/
struct A {
    friend static void call();
private:
    static void s_static_func() {
        n = new int(2);
        this_thread::sleep_for(chrono::seconds(2));
        cout << "before main\n";
    }
    static int* n;
};
int* A::n;

static void call() {
    A::s_static_func();
}

auto lam = [](void) -> int{
    call();
    return 0;
}();

int main(int arg, char** args) {
    cout << "main" << endl;
}


new handler

注册

  • 当 <font color=red>operator new</font> 调用==malloc==后, 发现没有内存, 会调用==new handler==, 所以new_handler的作用应该是释放一些不必要的内存


  • 可以注册自己的new handler
/** 
    void(__cdecl*)(void)    __cdecl是标准的c调用约定(函数从右往左压栈)
    using new_handler = void(__CLRCALL_PURE_OR_CDECL*)();

    /** 
        设置新的new handler, 返回旧的new handler
    */
    _CRTIMP2 new_handler __cdecl set_new_handler(_In_opt_ new_handler) noexcept;

    _NODISCARD _CRTIMP2 new_handler __cdecl get_new_handler() noexcept;
*/

void __cdecl mem_release(void){

}

int main(int arg, char** args){
    auto old_handler = set_new_handler(mem_release);
}


设计内存管理机制[^ann_1]

为什么要自己设计

c++中new的底层调用了c的malloc, 关于malloc会在后面探究, 这里先大致提一下:
    malloc本身很复杂, 效率也很高, 它本身就是一个内存管理器, 也可以称为内存池
    malloc在设计上会对给出的内存附加额外的内存
        当然用户是不知道的
        细心的程序员可能想到了free
            free函数只接收1个指针, 但为什么就可以准确释放呢?
            原因很可能是malloc的约定, 这个到后面细说
        
基于malloc的特点, 对容器来说, 特别是元素相邻的容器(如vector)
    如果直接用malloc, 则每次分配内存后都会附加其他内存
    由于每次push_back, 调用malloc后元素就不是紧紧相邻的, 这样vector管理元素的复杂度就大大提升
    所以要实现一个没有额外内存的管理器提供给vector


思路:
    降低malloc调用次数
        目的是减少额外内存
    有效利用手上的空间

下面开始就是一步一步从最简单的分配器开始, 一直演进到gcc中著名的内存池


内存管理第0步

  • 最重要的就是 <font color=red>接手new</font>, 从前面的学习中已经知道了, 可以为==类重载new==, 这样 <font color=red>申请内存的操作就到了自己的手中</font>, 下面开始为 <font color=red>1个单一的类设计内存管理</font>


内存管理单个类

分析:
    由于malloc有碎片, 所以为了减少malloc的调用, 但又不得不从malloc拿内存, 所以:
        要拿就拿够

思路:
    一次性向malloc索取一大块内存, 放到自己的手中
    当类new的时候, 先看手上有没有
        有就给出去指针
        没有就向malloc索取

    当delete时, 将内存再回收到自己的手中

    索取的内存用链表来管理
#define _MEM_ERROR_0 -1

#define _NEW_CODE_ \
    if (size <= 0)\
        throw _MEM_ERROR_0;\
    return A::alloc(size);

#define _DEL_CODE_  \
    if(ptr) \
        return dealloc(ptr);

class A {
public:


    void* operator new(size_t size) {
        _NEW_CODE_
    }

    void* operator new[](size_t size) {
        _NEW_CODE_
    }

    void operator delete(void* ptr) {
        _DEL_CODE_  
    }
    void operator delete[](void* ptr) {
        _DEL_CODE_
    }

private:
    static void make_list(A* begin, A* const end) {
        while (begin != end)
            (begin->next = begin + 1, ++begin);

        begin->next = nullptr;
    }

    static void* _malloc_chunk(size_t len) {
        size_t need_mem = A::chunk * len;

        size_t mod = need_mem / sizeof(A);

        A* need = static_cast<A*>(malloc(need_mem));
        
        // 取头尾指针, 包含尾指针
        A::make_list(need, reinterpret_cast<A*const>(need + mod - 1));

        return need;
    }

    static void* alloc(size_t len) {
        A* result = A::free_mem_start;

        if (!result)
            result = A::free_mem_start = static_cast<A*>(A::_malloc_chunk(len));

        A::free_mem_start = A::free_mem_start->next;

        return static_cast<void*>(result);
    }

    static void dealloc(void* ptr) {
        //将 已经调用过析构函数的废弃的ptr指向当前链表的头部
        static_cast<A*>(ptr)->next = A::free_mem_start;

        //再将头部的指针指向ptr
        A::free_mem_start = static_cast<A*>(ptr);

        /*
            这里无法将指针还给os(free), 因为无法确定ptr是不是malloc给出的合理指针
        */
    }
private:
    static constexpr int chunk = 4;

    static A* free_mem_start;
    
    // 相当于链表中的节点
    struct {
        A* next;
        int data;
    };
};
A* A::free_mem_start = nullptr;

int main(int arg, char** args) {
    {
        A* a = ::new A;
        A* b = ::new A;
        A* c = ::new A;
        cout << a << "\n";
        cout << b << "\n";
        cout << c << "\n";
    }
    cout << "***************************\n";
    
    {
        for (int i = -1; ++i < 11;) {
            A* a = new A;
            cout << a << endl;
        }
    }
}

/** 
00DEFF40
00DEF990
00DEFB18
***************************
00DEC130
00DEC138
00DEC140
00DEC148

00DF24B8
00DF24C0
00DF24C8
00DF24D0

00DF2468
00DF2470
00DF2478


    前3个直接调用的全局的operator new, 所以是malloc分配, 所以3个堆内存是不相邻的
    
    
    代码中设置的chunk为4, 所以每4次分配内存
    相邻的4次之间是紧紧相连的(A对象大小8个字节)


    存在的问题:
        为了实现链表, 在A的类型中加入了一个额外的指针next
        分析可以知道 在链表中的内存就只有2种状态:
            外界在用,即没在链表中
            外界没有在用, 即在链表中
            而A中的data和next实际就是这2种状态, 外界在用, 则next是无效的(data有效), 外界没有用, 则next有效(data无效)
            所以可以考虑 data, next共用同一块内存(union)

        修改:
            将 A中的struct 直接修改为union, 其他地方不要改, 测试如下:
*/
00DC7E28
00DC7E58
00DCBCD0
***************************
00DD1FC0
00DD1FC4
00DD1FC8
00DD1FCC

00DCBD00
00DCBD04
00DCBD08
00DCBD0C

00DC6150
00DC6154
00DC6158

/** 
    改为union后, A大小是4个字节, 所以每个元素大小是4,并且是相邻的
    
    现在测试代码再改一下
*/
int main(int arg, char** args){
    for (int i = -1; ++i < 11;) {
        A* a = new A;
        cout << a << endl;
        delete a;
    }
}
/** 
01286428
01286428
01286428
01286428
01286428
01286428
01286428
01286428
01286428
01286428
01286428

    发现a的地址始终是同一个, 说明是不断的从池中拿的
*/


上述实现的bug

问题:
    1. 只适用于 new/delete, 如果是 array new/array delete就有问题
    2. 如果A的大小 < void*, 则会有额外有多余的内存, 但这是合理的, 虽然看起来浪费了, 但却在灵活使用手上的内存
    

无法写出析构函数:
    原因是不可能找到最初的malloc的地址指针

原因:
    alloc 函数中:
        A::free_mem_start = A::free_mem_start->next   _code_a

    这句代码是以 1个sizeof(A)为单位往外给内存, 也就是alloc函数参数len的大小是sizeof(A)
    但如果是 new A[2], 那么传递到alloc时, len就是 2*sizeof(A)为8
        此时_code_a中的 free_mem_start应该移动2次
            即: A::free_mem_strat = A::free_mem_start->next->next

    相应的 在dealloc函数中, 如果还的是 A[2] 这个数组, 应该先在delete[]中将数组中的2个元素串起来, 再传给dealloc
    但可惜的是 delete[] arr_obj, 不会将arr_obj整个数组的大小传递过去
        

    所以如果牵扯到 new A[] 和 new A的交叉使用, 这个内存池是不可以的, 而且无法将内存归还给操作系统

解决:
    关于第1个交叉使用的问题, 可以在内存池内部维护 N*sizeof(A) 的链表, 这个也是后面gcc中一个分配器的实现
    第2个归还os的问题, 当前这种设计是不支持的, 但并不是所谓的内存泄露, 因为所有的内存都在自己的手上

    所以上面的版本只能对 new A()使用, 应该删除掉 new[]和delete[]


进化到版本2(通用到各个类)

/** 
    就是把内存相关的部分抽取出来
*/
template<int _N>
constexpr size_t _align_min() {
    static_assert(_N > 0, "通过");
    return (size_t)_N < sizeof(void*) ? sizeof(void*) : _N;
}

template<int _N>
constexpr size_t _check_chunk() {
    static_assert(_N > 0, "通过");
    return _N;
}

template<
    int Num,
    int chunk = 4, 
    size_t _unit = _align_min<Num>(), 
    size_t _chunk = _check_chunk<chunk>()>
class _alloc_ {
public: 
    void* alloc() {
        _Node* result = this->head;

        if (!result) {
            cout << _chunk << " " << _unit << endl;
            result = this->head = static_cast<_Node*>(::operator new(_chunk * _unit));

            cout << "malloc address: " << result << endl;

            for (; result != &this->head[chunk - 1];)
                (result->next = result + 1, ++result);

            result->next = nullptr;

            result = this->head;
        }
        this->head = this->head->next;

        return reinterpret_cast<void*>(result);
    }
    void dealloc(void* ptr) {
        cout << "回收: " << ptr << endl;
        reinterpret_cast<_Node*>(ptr)->next = this->head;
        this->head = reinterpret_cast<_Node*>(ptr);
    }

public:
    template<size_t _N>
    struct _tuple_ : _tuple_<_N - 1> {
        char data;
    };

    template<>
    struct _tuple_<0> {};

    using _Data = _tuple_<_unit>;
    using _Data_p = typename add_pointer<_Data>::type;

    union _Node{
        _Data data;
        _Node* next;
    };
    
    _Node* head;
};



/** 
    交差delete测试1
*/
int main(int arg, char** args) {
    _alloc_<2> all{};

    for (int i = -1; ++i < 10;){
        auto p = all.alloc();
        cout <<"fir:" << p << endl;

        all.dealloc(p);  //只回收1次

        p = all.alloc();
        cout <<"sec:" << p << endl;
    }
}
/** 
4 4
malloc address: 013BC130
fir:013BC130
回收: 013BC130
sec:013BC130
fir:013BC134
回收: 013BC134
sec:013BC134
fir:013BC138
回收: 013BC138
sec:013BC138
fir:013BC13C
回收: 013BC13C
sec:013BC13C
4 4
malloc address: 013C1F90
fir:013C1F90
回收: 013C1F90
sec:013C1F90
fir:013C1F94
回收: 013C1F94
sec:013C1F94
fir:013C1F98
回收: 013C1F98
sec:013C1F98
fir:013C1F9C
回收: 013C1F9C
sec:013C1F9C
4 4
malloc address: 013B6428
fir:013B6428
回收: 013B6428
sec:013B6428
fir:013B642C
回收: 013B642C
sec:013B642C

    这个测试结果没有问题
*/





/** 
    交差测试2
*/
int main(int arg, char** args) {
    
    _alloc_<2> all{};

    for (int i = -1; ++i < 10;){
        auto p = all.alloc();
        cout <<"fir:" << p << endl;

        p = all.alloc();
        cout <<"sec:" << p << endl;

        all.dealloc(p);  //只回收1次
    }
}
/** 
4 4
malloc address: 00A01FC0
fir:00A01FC0
sec:00A01FC4
回收: 00A01FC4
fir:00A01FC4
sec:00A01FC8
回收: 00A01FC8
fir:00A01FC8
sec:00A01FCC
回收: 00A01FCC
fir:00A01FCC
4 4
malloc address: 009FBD00
sec:009FBD00
回收: 009FBD00
fir:009FBD00
sec:009FBD04
回收: 009FBD04
fir:009FBD04
sec:009FBD08
回收: 009FBD08
fir:009FBD08
sec:009FBD0C
回收: 009FBD0C
fir:009FBD0C
4 4
malloc address: 009F7E28
sec:009F7E28
回收: 009F7E28
fir:009F7E28
sec:009F7E2C
回收: 009F7E2C
fir:009F7E2C
sec:009F7E30
回收: 009F7E30


    也没有问题
*/


完善版本2

template<int _N>
constexpr size_t _align_min() {
    static_assert(_N > 0, "通过");
    return (size_t)_N < sizeof(void*) ? sizeof(void*) : _N;
}

template<int _N>
constexpr size_t _check_chunk() {
    static_assert(_N > 0, "通过");
    return _N;
}

template<
    int Num,
    int chunk = 4, 
    size_t _unit = _align_min<Num>(), 
    size_t _chunk = _check_chunk<chunk>()>
class _alloc_ {
public: 
    void* alloc() {
        _Node* result = this->head;

        if (!result) {
            cout << _chunk << " " << _unit << endl;
            result = this->head = static_cast<_Node*>(::operator new(_chunk * _unit));

            cout << "malloc address: " << result << endl;

            for (; result != &this->head[chunk - 1];)
                (result->next = result + 1, ++result);

            result->next = nullptr;

            result = this->head;
        }
        this->head = this->head->next;

        return reinterpret_cast<void*>(result);
    }
    void dealloc(void* ptr) {
        cout << "回收: " << ptr << endl;
        reinterpret_cast<_Node*>(ptr)->next = this->head;
        this->head = reinterpret_cast<_Node*>(ptr);
    }
    
    template<typename T>
    static _alloc_& get() {
        static _alloc_<sizeof(T)> mem;
        cout <<"memory static alloca: " << &mem << endl;
        return mem;
    }
private:
    template<size_t _N>
    struct _tuple_ : _tuple_<_N - 1> {
        char data;
    };

    template<>
    struct _tuple_<0> {};

    using _Data = _tuple_<_unit>;
    using _Data_p = typename add_pointer<_Data>::type;

    union _Node{
        _Data data;
        _Node* next;
    };
    
    _Node* head;
};



#define _US_LB_ALLOCATOR_NEW(_Cls) \
    void* operator new(size_t size) {   \
        return _alloc_<sizeof(_Cls)>::get<_Cls>().alloc();\
    }\


#define _US_LB_ALLOCATOR_DEL(_Cls) \
    void operator delete(void* ptr) { \
        return _alloc_<sizeof(_Cls)>::get<_Cls>().dealloc(ptr);\
    }\


#define _US_LB_ALLCATOR(_Cls)\
_US_LB_ALLOCATOR_NEW(_Cls)\
_US_LB_ALLOCATOR_DEL(_Cls)

class A {
public:
    _US_LB_ALLCATOR(A)

private:
    int a;
    int b;
};


class B {
public:
    _US_LB_ALLCATOR(B)
private:
    int b;
};



int main(int arg, char** args) {

    for (int i = -1; ++i < 4;)
    {
        auto p_a = new A();
        cout <<"fir p_a" << p_a << endl;

        p_a = new A();
        cout <<"sec:" << p_a << endl;

        
        auto p_b = new B();
        cout << "fir p_b: " << p_b << endl;
        delete p_b;

        p_b = new B();
        cout << "sec p_b: " << p_b << endl;
        delete p_b;

        delete p_a;
    }
}


/** 
memory static alloca: 002F03C4
4 8
malloc address: 00DEC130
fir p_a00DEC130
memory static alloca: 002F03C4
sec:00DEC138
memory static alloca: 002F06E4
4 4
malloc address: 00DE6428
fir p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F06E4
sec p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F03C4
回收: 00DEC138
memory static alloca: 002F03C4
fir p_a00DEC138
memory static alloca: 002F03C4
sec:00DEC140
memory static alloca: 002F06E4
fir p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F06E4
sec p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F03C4
回收: 00DEC140
memory static alloca: 002F03C4
fir p_a00DEC140
memory static alloca: 002F03C4
sec:00DEC148
memory static alloca: 002F06E4
fir p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F06E4
sec p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F03C4
回收: 00DEC148
memory static alloca: 002F03C4
fir p_a00DEC148
memory static alloca: 002F03C4
4 8
malloc address: 00DF2508
sec:00DF2508
memory static alloca: 002F06E4
fir p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F06E4
sec p_b: 00DE6428
memory static alloca: 002F06E4
回收: 00DE6428
memory static alloca: 002F03C4
回收: 00DF2508
*/


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • 本文转自:https://www.cnblogs.com/lancidie/archive/2011/08/05/...
    Nrocinu阅读 1,334评论 0 1
  • 2 内存泄漏 2.1 C++中动态内存分配引发问题的解决方案 假设我们要开发一个String类,它可以方便地处理字...
    Nrocinu阅读 199评论 0 0
  • layout: posttitle: 重识newcategories: C/C++description: 重识n...
    超哥__阅读 634评论 0 0
  • 应用程序的设计中,我们所说的内存管理就是将系统要处理的内存分配和释放接管过来,内存池是常用的一种设计思路。内存池是...
    GreatJu阅读 328评论 0 2
  • layout: posttitle: 重识newcategories: C/C++description: 重识n...
    超哥__阅读 199评论 0 0