1

上一章完成的两个 allocator 必须为不同的 classes 重写一遍几乎相同的 member operator new 和 member operator delete,应该有方法将一个总是分配特定尺寸块的 memory allocator 概念包装起来,使它容易被重复使用。
以下展示一种方式,每个 allocator object 都是个分配器,它维护一个 free-list;不同的 allocator objects 维护不同的 free-list。

static allocator

#include <iostream>
#include <cstdlib>
#include <string>
#include <complex>

using std::string;
using std::complex;
using std::cout;
using std::endl;

class allocator
{
private:
    struct obj {
        struct obj *next; // embedded pointer
    };

public:
    void *allocate(size_t);
    void deallocate(void*, size_t);

private:
    obj *freeStore = nullptr;
    const int CHUNK = 5;  // 小一些方便观察
};

// 操作系统会尽量保证每次申请 CHUNK * size 内存时是连续的, 但两次之间无此承诺
void *allocator::allocate(size_t size)
{
    obj *p;

    if (!freeStore)
    {
        // linked list 为空,于是申请一大块内存
        size_t chunk = CHUNK * size;
        freeStore = p = static_cast<obj*>(malloc(chunk));

        // 将分配得来的一大块当作 linked list 般,小块小块串起来
        for (int i=0; i<(CHUNK-1); ++i)
        {
            p->next = reinterpret_cast<obj*>((char*)p + size);
            p = p->next;
        }
    }

    p = freeStore;
    freeStore = freeStore->next;

    return p;
}

void allocator::deallocate(void* p, size_t)
{
    // 将 *p 收回插入 free list 前端
    static_cast<obj*>(p)->next = freeStore;
    freeStore = static_cast<obj*>(p);
}

//=========================

class Foo {
public:
    long L;
    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, size_t size)
    {
        return myAlloc.deallocate(pdead, size);
    }
};

allocator Foo::myAlloc;

class Goo {
public:
    complex<double> c;
    string str;
    static allocator myAlloc;

public:
    Goo(const complex<double> &x) : c(x)
    { }

    static void *operator new(size_t size)
    {
        return myAlloc.allocate(size);
    }

    static void operator delete(void *pdead, size_t size)
    {
        return myAlloc.deallocate(pdead, size);
    }
};

allocator Goo::myAlloc;

void func_1()
{
    Foo *p[100];

    cout << "sizeof(Foo) = " << sizeof(Foo) << endl;

    for (size_t i=0; i<23; ++i)
    {
        if (i != 0 && i % 5 == 0)
            cout << endl;

        p[i] = new Foo(i);
        cout << p[i] << ' ' << p[i]->L << endl;
    }

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

void func_2()
{
    Goo *p[100];

    cout << "sizeof(Foo) = " << sizeof(Goo) << endl;

    for (size_t i=0; i<17; ++i)
    {
        if (i != 0 && i % 5 == 0)
            cout << endl;

        p[i] = new Goo(complex<double>(i, i));
        cout << p[i] << ' ' << p[i]->c << endl;
    }

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

int main()
{
    func_1();

    cout << "============" << endl;

    func_2();

    return 0;
}

输出:[注意观察每五次输出之间,地址的连续性]

sizeof(Foo) = 4
0xeb80e8 0
0xeb80ec 1
0xeb80f0 2
0xeb80f4 3
0xeb80f8 4

0xeb8108 5
0xeb810c 6
0xeb8110 7
0xeb8114 8
0xeb8118 9

0xeb8128 10
0xeb812c 11
0xeb8130 12
0xeb8134 13
0xeb8138 14

0xeb8148 15
0xeb814c 16
0xeb8150 17
0xeb8154 18
0xeb8158 19

0xeb8168 20
0xeb816c 21
0xeb8170 22
============
sizeof(Foo) = 40
0xeb8188 (0,0)
0xeb81b0 (1,1)
0xeb81d8 (2,2)
0xeb8200 (3,3)
0xeb8228 (4,4)

0xeb8258 (5,5)
0xeb8280 (6,6)
0xeb82a8 (7,7)
0xeb82d0 (8,8)
0xeb82f8 (9,9)

0xeb8408 (10,10)
0xeb8430 (11,11)
0xeb8458 (12,12)
0xeb8480 (13,13)
0xeb84a8 (14,14)

0xeb84d8 (15,15)
0xeb8500 (16,16)

这比先前的设计干净多了, application classed 不再与内存分配细节纠缠不清,所有相关细节都让 allocator 去操心,我们的工作是让 application classes 正确工作。

macro for static allocator

#include <iostream>
#include <cstdlib>
#include <string>
#include <complex>

using std::string;
using std::complex;
using std::cout;
using std::endl;

class allocator
{
private:
    struct obj {
        struct obj *next; // embedded pointer
    };

public:
    void *allocate(size_t);
    void deallocate(void*, size_t);

private:
    obj *freeStore = nullptr;
    const int CHUNK = 5;  // 小一些方便观察
};

// 操作系统会尽量保证每次申请 CHUNK * size 内存时是连续的, 但两次之间无此承诺
void *allocator::allocate(size_t size)
{
    obj *p;

    if (!freeStore)
    {
        // linked list 为空,于是申请一大块内存
        size_t chunk = CHUNK * size;
        freeStore = p = static_cast<obj*>(malloc(chunk));

        // 将分配得来的一大块当作 linked list 般,小块小块串起来
        for (int i=0; i<(CHUNK-1); ++i)
        {
            p->next = reinterpret_cast<obj*>((char*)p + size);
            p = p->next;
        }
    }

    p = freeStore;
    freeStore = freeStore->next;

    return p;
}

void allocator::deallocate(void* p, size_t)
{
    // 将 *p 收回插入 free list 前端
    static_cast<obj*>(p)->next = freeStore;
    freeStore = static_cast<obj*>(p);
}

// DECLARE_POOL_ALLOC -- used in class definition
#define DECLARE_POLL_ALLOC()                    \
public:                                         \
void *operator new(size_t size)                 \
{                                               \
    return myAlloc.allocate(size);              \
}                                               \
void operator delete(void *pdead, size_t size)  \
{                                               \
    return myAlloc.deallocate(pdead, size);     \
}                                               \
protected:                                      \
    static allocator myAlloc;

// IMPLEMENT_POOL_ALLOC -- used in class implementcation file
#define IMPLEMENT_POOL_ALLOC(class_name)        \
    allocator class_name::myAlloc;

//=========================

class Foo {
    DECLARE_POLL_ALLOC();
public:
    long L;

public:
    Foo(long l) : L(l)
    { }
};

IMPLEMENT_POOL_ALLOC(Foo);

class Goo {
    DECLARE_POLL_ALLOC();
public:
    complex<double> c;
    string str;

public:
    Goo(const complex<double> &x) : c(x)
    { }
};

IMPLEMENT_POOL_ALLOC(Goo);

void func_1()
{
    Foo *p[100];

    cout << "sizeof(Foo) = " << sizeof(Foo) << endl;

    for (size_t i=0; i<23; ++i)
    {
        if (i != 0 && i % 5 == 0)
            cout << endl;

        p[i] = new Foo(i);
        cout << p[i] << ' ' << p[i]->L << endl;
    }

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

void func_2()
{
    Goo *p[100];

    cout << "sizeof(Foo) = " << sizeof(Goo) << endl;

    for (size_t i=0; i<17; ++i)
    {
        if (i != 0 && i % 5 == 0)
            cout << endl;

        p[i] = new Goo(complex<double>(i, i));
        cout << p[i] << ' ' << p[i]->c << endl;
    }

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

int main()
{
    Foo *pF[100];
    Goo *pG[100];

    cout << "sizeof(Foo)=" << sizeof(Foo) << endl;
    cout << "sizeof(Goo)=" << sizeof(Goo) << endl;

    for (int i=0; i<23; ++i)
    {
        pF[i] = new Foo(i);
        pG[i] = new Goo(i);

        cout << pF[i] << ' ' << pF[i]->L << "\t";
        cout << pG[i] << ' ' << pG[i]->c << "\n";
    }

    for (int i=0; i<23; ++i)
    {
        delete pF[i];
        delete pG[i];
    }

    return 0;
}

输出:

sizeof(Foo)=4
sizeof(Goo)=40
0x7a80e8 0    0x7a8108 (0,0)
0x7a80ec 1    0x7a8130 (1,0)
0x7a80f0 2    0x7a8158 (2,0)
0x7a80f4 3    0x7a8180 (3,0)
0x7a80f8 4    0x7a81a8 (4,0)
0x7a81d8 5    0x7a8398 (5,0)
0x7a81dc 6    0x7a83c0 (6,0)
0x7a81e0 7    0x7a83e8 (7,0)
0x7a81e4 8    0x7a8410 (8,0)
0x7a81e8 9    0x7a8438 (9,0)
0x7a82d8 10    0x7a8468 (10,0)
0x7a82dc 11    0x7a8490 (11,0)
0x7a82e0 12    0x7a84b8 (12,0)
0x7a82e4 13    0x7a84e0 (13,0)
0x7a82e8 14    0x7a8508 (14,0)
0x7a82f8 15    0x7a8538 (15,0)
0x7a82fc 16    0x7a8560 (16,0)
0x7a8300 17    0x7a8588 (17,0)
0x7a8304 18    0x7a85b0 (18,0)
0x7a8308 19    0x7a85d8 (19,0)
0x7a8318 20    0x7a8608 (20,0)
0x7a831c 21    0x7a8630 (21,0)
0x7a8320 22    0x7a8658 (22,0)

global allocator (with multiple free-lists)

将前述 allocator 进一步发展为具备 16 条 free-list, 并因此不再以 application classes 内的static 呈现,而是一个 global allocator -- 这就是 G2.9 的 std::alloc 的雏形。
image.png


TianSong
734 声望138 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧