C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)

C++ 内存管理
侯捷老师的课程内容,做了个简单的记录

文章目录

  • 第一講:C++ 內存構件
    • 四个层面
    • 基本構件 new delete expression
      • malloc的cookie机制
    • arrary new、arrary delete
      • operator new[]和operator delete[]
    • placement new —— 定位new
    • 重载
      • basic_string 使用new(extra) 扩充申请量
    • per-class allocator 每个class一个 allocator
      • per-class allocator 1
      • per-class allocator 2
      • static allocator 3
      • macro for static allocator 4
    • new handler

第一講:C++ 內存構件 四个层面 C++应用程序,使用memory的途径
SGI STL G2.9 用的allocate (SGI) 是直接调用的malloc(),图有些差错
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

C++ memory primitives
分配 释放 属性 是否重载
malloc() free() C函数 不可
new delete C++表达式 不可
::operator new() ::operator delete() C++函数
alloctor allocate() alloctor deallocate() C++标准库 可自由设计并以之搭配任何容器
基本構件 new delete expression new expression
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

delete expression
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

malloc的cookie机制
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片
在malloc为用户分配内存的时候,除了分配用户本身的内存,还会在内存前后加上两个cookie,来记录分配了多少内存,这样在调用free函数的时候才能准确的回收内存。因此每次调用malloc函数都会产生cookie消耗。
而new操作符中的array new,为了记录需要调用多少次析构函数,会在分配的内存前记录分配了多少个对象。下面的图更好理解一点:
malloc和new的内存分布布局
上图展示了我们在运行代码示例一之后分配的内存,其中61H是malloc设置的cookie,表示malloc总共分配给用户的内存大小;而00481c30地址所指的3,则是new设置的其分配对象的个数。也就是说,每次调用new操作符的时候,都会分配一些额外的内存来存放所分配内存信息。
arrary new、arrary delete 对于 Complex 是POD类型, delete 调用3次和1次没区别
string类 delete调用少了,会发生泄漏
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

operator new[]和operator delete[]
首先来看下面这段代码:
// 代码示例二 #include using namespace std; class Demo { private: int a, b, c; public: Demo() : a(0), b(0), c(0) { cout << "constructor" << endl; } ~Demo() { cout << "destructor" << endl; } }; int main() { Demo *p = new Demo[3]; delete[] p; return 0; } /* 输出: constructor constructor constructor destructor destructor destructor */

上面的代码示例二展示了array new和array delete的用法,可以看到,其中调用了3次构造函数和3次析构函数。如果我们将delete[] p改为delete p,结果会是什么样呢?
将delete[]改为delete
将delete[] p改为delete p之后,会出现上图的错误,invalid pointer。为什么会出现这种情况呢,前面我们提到过,在使用new操作符分配一个数组时,会在分配的数组前面多分配几个字节(视环境而定侯捷老师说是4个字节,但是在我的环境下面是8个字节),再来看一下前面那个图:
我们在运行了代码示例2之后,实际分配的内存如上图所示(仅仅是为了说明,地址并不准确)。在运行Demo *p =new Demo[3]之后,返回的指针p是0x00481c34,在00481c34之前还有用来存放对象数量的内存,4个字节或者8个字节,图中展示为8个字节。
在调用delete[] p的时候,会调用operator delete[]函数,而传入operator delete[]函数的指针其实是从0x00481c30开始的,并不是从对象真正的地址开始,因为new[]申请的内存是从0x00481c30开始的。
placement new —— 定位new
  • placement new允群我们将object建媾於 allocated memory 中。
  • 没有所谓 placement delete﹐因爲placement new根本没分配memory,亦或称呼 舆placement new 对应的operator delete爲 placement delete.
注意﹐開於“placement new”,
或指new§
或指::operator new(size, void*)
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

重载 C++ 应用程序,分配内存的途径
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

C++ 容器,分配内存的途径
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

重载 new() / delete()
我们可以重截class member operator new()﹐寓出多佃版本﹐前提是每一版本的馨明都必须有嗳特的参敷列﹐其中第一参数必须是size_t,其余参数以new所指定的placement arguments爲初值·出现於 new …小括虢内的便是所谓placement arguments 。
我们也可以重戴class member operator delete(),写出多个版本·但它绝不会被delete 调用。只有常new所调用的ctor抛出exception,才会调用这些重载的operator delete()·它只可能被这样调用﹐主要用来归还未能完全创建成功的 object所占用的memory 。
例子
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

basic_string 使用new(extra) 扩充申请量
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

per-class allocator 每个class一个 allocator 想利用类内重载operator new去接管内存的分配,然后利用内存池的观念【即创建出一大段连续空间的内存,然后将其切割成一小段一小段】,将创建的元素对象放在内存池切分好的各分段小内存片中,这样避免了多次调用new而造成生成多个带有cookie的内存空间。通过内存池的观念,可以生成一大段只带有两个头尾cookie的内存空间,而该一大段内存空间又被切分成每一小段的内存空间,且其中的每一小段内存空间片都可以共享这一整体的cookie信息。
per-class allocator 1
per-class allocator 1
  • 因为为了能将一大段内存空间切分成一小段一小段,然后通过单向链表的形式串接起来,所以必须多引入一个Screen* next指针。但这又会增加class Screen的大小【增加了4字节】。
    另外,static Screen* freeStore 和 static const int screenChunk是静态成员变量,是声明class Screen就创建出来的了【而且被其所有类对象共享的】,并不是在创建每一个Screen类对象时才被创建出来,因而并不计算入类对象的大小中【所以生成的每一个类对象其大小为4+4=8】。
#include #include namespace jj04 { //ref. C++Primer 3/e, p.765 //per-class allocator class Screen { public: Screen(int x) : i(x) { }; int get() { return i; }void* operator new(size_t); voidoperator delete(void*, size_t); //(2) //! voidoperator delete(void*); //(1) 二擇一. 若(1)(2)並存,會有很奇怪的報錯 (摸不著頭緒) private: Screen* next; static Screen* freeStore; static const int screenChunk; private: int i; }; Screen* Screen::freeStore = 0; const int Screen::screenChunk = 24; void* Screen::operator new(size_t size) { Screen *p; if (!freeStore) { //linked list 是空的,所以攫取一大塊 memory //以下呼叫的是 global operator new size_t chunk = screenChunk * size; freeStore = p = reinterpret_cast(new char[chunk]); //將分配得來的一大塊 memory 當做 linked list 般小塊小塊串接起來 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)//(1) void Screen::operator delete(void *p, size_t) //(2)二擇一 { //將 deleted object 收回插入 free list 前端 (static_cast(p))->next = freeStore; freeStore = static_cast(p); }//------------- void test_per_class_allocator_1() { cout << "\ntest_per_class_allocator_1().......... \n"; cout << sizeof(Screen) << endl; //8 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< N; ++i) delete p[i]; } } //namespace -----------------------------------------------

per-class allocator 2
per-class allocator 2
  • 这个版本通过union关键字来减少使用next而所占耗的内存
  • 注意:这两个版本的operator delete操作都没有将内存空间回收还回给系统,而是仍然存在的。虽然operator delete操作没有将这些分配的内存空间释放掉,但其仍在控制中即仍可继续重新使用【只不过freeStore或headOfFreeList此时又重新跑回整个一大块内存空间的头端】,所以不算内存泄漏!
//---------------------------------------------------- #include #include namespace jj05 { //ref. Effective C++ 2e, item10 //per-class allocator class Airplane {//支援 customized memory management private: struct AirplaneRep { unsigned long miles; char type; }; private: union { AirplaneRep rep; //此針對 used object Airplane* next; //此針對 free list }; 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 voidoperator 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 有效,就把list頭部移往下一個元素 if (p) headOfFreeList = p->next; else { //free list 已空。配置一塊夠大記憶體, //令足夠容納 BLOCK_SIZE 個 Airplanes Airplane* newBlock = static_cast (::operator new(BLOCK_SIZE * sizeof(Airplane))); //組成一個新的 free list:將小區塊串在一起,但跳過 //#0 元素,因為要將它傳回給呼叫者。 for (int i = 1; i < BLOCK_SIZE-1; ++i) newBlock[i].next = &newBlock[i+1]; newBlock[BLOCK_SIZE-1].next = 0; //以null結束// 將 p 設至頭部,將 headOfFreeList 設至 // 下一個可被運用的小區塊。 p = newBlock; headOfFreeList = &newBlock[1]; } return p; }// operator delete 接獲一塊記憶體。 // 如果它的大小正確,就把它加到 free list 的前端 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(deadObject); carcass->next = headOfFreeList; headOfFreeList = carcass; }//------------- void test_per_class_allocator_2() { cout << "\ntest_per_class_allocator_2().......... \n"; cout << sizeof(Airplane) << endl; //8size_t const N = 100; Airplane* p[N]; for (int i=0; i< N; ++i) p[i] = new Airplane; //隨機測試 object 正常否 p[1]->set(1000,'A'); p[5]->set(2000,'B'); p[9]->set(500000,'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]; } } //namespace //-----

static allocator 3
赏你受困於必须爲不同的classes重写一遍几乎相同的member operator new和 member operator delete时﹐应该有方法将一个总是分配特定尺寸之区块的memory allocator概念包装起来﹐使它容易被重愎使用。以下展示一擂作法﹐每固allocator object都是个分配器﹐它体内维护一个ree-lists ; 不同的allocator objects维护不同的free-lists 。
将分配特定尺寸区块的memory allocator包装成一个class allocator,
这样每个allocator object都是个分配器,体内维护一个free lists,
不同类(如下面的class Foo,class Goo等) 里面调用生成的各自allocator objects维护不同的free lists。
另外,class Foo或class Goo中,应注意到:
static allocator myAlloc,即myAlloc必须是个静态成员变量,且在类外定义(赋初值)。如果其不是静态成员变量时,则在构建class Foo类对象时,是没办法调用到myAlloc的。因为非静态成员变量只能通过对象调用【但此时Foo对象还没生成又如何调用!!】。而myAlloc又是用来生成Foo类对象的,所以得通过类名调用即应设置为static类型。
class allocator{ private: struct obj{ struct obj* next; }; public: static void* allocate(size_t); static void deallocate(void*, size_t); private: obj* freeStore = nullptr; const int CHUNK = 5; // 标准库一般设置为20 }; void* allocator::allocate(size_t size){ obj* p; if(!freeStore){ // linked list为空,则申请一大块 size_t chunk = CHUNK * size; freeStore = p = (obj*)malloc(chunk); // 这里直接调用malloc进行分配空间// 将分配的一大块切分成5小段,并串接起来 for(int i = 0; i < (CHUNK - 1); ++i){ p->next = (obj*)((char*)p + size); p = p->next; // 上面这两步相当于p->next = p + 1, // 只不过这里需要适应不同的类下的操作,因而设置成这种形式!! } p->next = nullptr; // 最后一小段的下一个位置指向空 } p = freeStore; freeStore = freeStore->next; return p; } void allocator::deallocate(void* p, size_t){ // 将要删除的*p的位置调整为free list的头端 ((obj*)p)->next = freeStore; freeStore = (obj*)p; }

C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

macro for static allocator 4
定义两个宏完成功能
在类内进行宏声明,在类外进行宏定义【告诉编译器传入参数的class type】
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

// DECLARE_POOL_ALLOCused in class definition #define DECLARE_POOL_ALLOC()\ public:\ void* operator new(size_t size){ return myAlloc.cllocate(size); }\ void operator delete(void* p){ myAlloc.deallocate(p, 0); }\ protected:\ static allocator myAlloc; // IMPLEMENT_POOL_ALLOCused in class implementation file #define IMPLEMENT_POOL_ALLOC(class_name)\ allocator class_name::myAlloc;

new handler 当operator new没能力爲你分配出你所申请的memory,会抛一倜std::bad_alloc exception 。某些老旧的编译器则是返回0,你仍然可以令编译器那么做︰
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

设计良好的new handler
  • 让更多 memory 可用
  • 调用 abort() 或 exit()
设置 new handler —— set_new_handler
C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

=default , =delete
operator new 不能被default(没有默认版本),可用被delete(禁用)
【C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)】C++笔记|C++ 内存管理 —— 第一講(C++ 內存構件)
文章图片

    推荐阅读