C++内存管理之自制分配器

Posted by kalos Aner on November 13, 2024

C++内存管理之自制分配器

1、简单的内存池

内存管理的目的主要是为了节约时间和空间,虽然调用 malloc 并不慢,但是也应该尽可能地减少调用它的次数。减少调用 malloc 的调用次数可以通过一次性地调用大块内存,这样每次需要的时候只需要把没用到的内存分配出去即可,这种方法称之为内存池,如下例所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <cstddef>
#include <iostream>
using namespace std;

class Screen {
public:
	Screen(int x) : i(x) {};
	int get() {return i;}
	
	void* operator new(size_t);
	void operator delete(void*, size_t);

private:
	Screen* next;
    static Screen* freeStore;
    static const int screenChunk;
	int i;
}
Screen* Screen::freeStore = 0;
const int Screen::screenChunk = 24;
void* Screen::operator new(size_t size) {
    Screen* p;
    if (!freeStore) {
        size_t chunk = screenChunk * size; 
        freeStore = p = reinterpret_cast<Screen*>(new char[chunk]);
        for (; p != &freeStore[screenChunk - 1]; ++ p) {
            p->next = p + 1;
        }
        p->next = 0;
    }
    p = freeStore;
    freeStore = freeStore->next;
    return p;
}
// 不进行真正的回收,仅仅把想要回收的内存当作储备的可分配内存
void Screen::operator delete(void* p, size_t) {
    (static_cast<Screen*>(p))->next = freeStore;
    freeStore = static_cast<Screen*>(p);
}

但是这种设计会使得空间开销增大一倍。

另外一种方法如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class Airplane {
private:
	struct AirplaneRep {
		unsigned long miles;
		char type;
	};
private:
    union {
        AiplaneRep rep;
        Airplane* next;
    };
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;
const int Airplane::BLOCK_SIZE = 512;
void* Airplane::operator new(size_t size) {
    // 如果大小有误(当发生继承时可能会出现大小有误),转交给::operator new()
    if (size != sizeof(Airplane))
        return ::operator new(size);
   	Airplane* p = headOfFreeList;
    // 如果p还有效则下移,否则申请大块内存
    if (p) {
        headOfFreeList = p->next;
    } else {
        Airplane* newBlock = static_cast<Airplane*> (::operator new(BLOCK_SIZE* sizeof(Airplane)))
        for (int i = 1; i < BLOCK_SIZE - 1; ++ i) {
            newBlock[i].next = &newBlock[i + 1];
        }
        newBlock[BLOCK_SIZE - 1].next = 0;
        p = newBlock;
        headOfFreeList = &newBlock[1];
    }
	return p;
}
void Airplane::operator delete(void* deadObject, size_t size) {
    if (deadObject == 0) return ;
    if (size != sizeof(Airplane)) {
        ::operator delete(deadObject);
        return ;
    }
    Airplane* carcass = static_cast<Airplane*>(deadObject);
    carcass->next = headOfFreeList;
    headOfFreeList = carcass;
}

结构体 AirplaneRep 有 16 个字节,但是它只是在类 Airplane 中声明,所以类 Airplane 只有一个 union 占用16字节内存。这样当一个 Airplane 对象中没有数据时可以用 next 指针指向下一段空间,如果 Airplane 对象被分配数据了则使用 rep 变量存储数据。

2、简版 allocator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class allocator {
private:
	struct obj {
		struct obj* next;
	};
public:
	void* allocate(size_t);
	void deallocate(void*, size_t);
private:
	obj* freeStore = nullptr;
	const int CHUNK = 5;
};
void allocator::deallocate(void* p, size_t) {
    ((obj*)p)->next = freeStore;
    freeStore = (obj*)p;
}
void* allocator::allocate(size_t size) {
    obj* p;
    if (!freeStore) {
        size_t chunk = CHUNK * size;
        // 申请 CHUNK 块空间,并把空间类型设置为 obj 指针
        freeStore = p = (obj*)malloc(chunk);
        
        for (int i = 0; i < CHUNK - 1; ++ i) {
            p->next = (obj*)((char*)p + size);
            // 每次都把下一块空间当作一个obj指针用
            p = p->next;
        }
        p->next = nullptr;
    }
    p = freeStore;
    freeStore = freeStore->next;
    return p;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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, size_t size) {
        return myAlloc.deallocate(pdead, size);
    }
};
allocator Foo::myAlloc;

目的:增加可复用行。

3、macro for static allocator

1
2
3
4
5
6
7
8
9
10
11
// DECLARE_POOL_ALLOC -- used in class definition
#define DECLARE_POOL_ALLOC() \
public: \
    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);} \
protected: \
	static allocator myAlloc;
	
// IMPLEMENT_POOL_ALLOC -- used in class implementation file
#define IMPLEMENT_POOL_ALLOC(class_name) \
allocator class_name::myAlloc;

目的:简化代码量。

4、异常处理

如果 operator new 申请 memory 失败,会抛出 std::bad_alloc exception(某些古老的编译器会返回0)。程序抛出 exception 之前会先调用一个可指定的 handler,可以通过下面方法设置 handler :

1
2
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();

如下代码会反复调用 new_handler:

1
2
3
4
5
6
7
8
9
10
11
12
13
void* operator new(size t size, const std:nothrow_t&)
_THROW0(){
    // try to allocate size bytes
    void *p;
    while((p = malloc(size)) == 0) {
        // buy more memory or return null pointer
        _TRY_BEGIN
            if(_callnewh(size) == 0) break;
        _CATCH( td::bad alloc)return (0);
        _CATCH_END
    }
    return (p);
}

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

  • 使得有更多的 memory 可用
  • 调用 abort() 或 exit()

例如:

1
2
3
4
5
6
7
8
9
void noMoreMemory() {
	cerr << "out of memory" << endl;
	abort();
}
void main() {
    set_new_handler(noMoreMemory);
    int* p = new int[INT_MAX];
    assert(p);
}

abort() 会直接中止 operator new。