• 4. C++内存管理


    C++内存管理

    第一讲 malloc/free

    1. malloc算法对于系统运行的效率非常重要,DL malloc是很有效率的一个算法

    2. 越使用底层的功能接口,可能程序的移植性越差

    3. 内存分配工具

      分配释放种类可否重载
      malloc()free()C函数不可
      newdeleteC++表达式不可
      ::operator new()::operator delete()C++函数
      allocator ::allocate()allocator::deallocate()C++标准库可以自由设计并搭配容器
    4. 使用范例

      // 以下均为申请512个字节的内存空间
      void *p1 = malloc(512);
      free(p1);
      
      complex* p2 = new complex;
      delete p2;
      // 以下实质调用的是malloc和free
      void *p3 = ::operator new(512);
      ::operator delete(p3);
      
      // allocator有接口的标准,但是不同的编译器实现的标准可能存在差异
      #ifdef _MSC_VER
      	//以下函数都是non-static,要通过object调用,以下分配3个ints.
      	int* p4 = allocator().allocate(3,(int*)0);// 第二参数默认值
      	allocator().deallocate(p4,3);
      #endif
      #ifdef	_BORLANDC_
      	//以下函数都是non-static,要通过object调用,分配5个整数类型空间
      	int* p4 = allocator().allocate(5);// ()表示创建临时对象
      	allocator().deallocate(p4,5);
      #endif
      #ifdef _GNUC_//以下函数都是static
      	// 分配512个无指定类型的字节
      	void* p4 = alloc::allocate(512);
      	alloc::deallocate(p4,512);
      	//以下兩函數都是 non-static,定要通過 object 調用。以下分配 7 個 ints.    
      	void* p4 = allocator().allocate(7); 
          allocator().deallocate((int*)p4,7);     
      	
          //以下兩函數都是 non-static,定要通過 object 調用。以下分配 9 個 ints.	
      	void* p5 = __gnu_cxx::__pool_alloc().allocate(9); 
          __gnu_cxx::__pool_alloc().deallocate((int*)p5,9);	
      #endif
      
      
      • 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
    5. new的本质(分配–转型–实例化)

      Complex *pc = new Complex(1, 2);
      // 用c语言进行其编译功能的解释
      Complex *pc;
      try{
          void *mem = operator new(sizeof(Complex));//分配内存,实质调用malloc
          pc = static_cast(mem);// 内存转型,赋值给指针
          pc->Complex::Complex(1, 2);// 调用构造函数实例化内存(赋值)
          
      }catch(std::bad_alloc){
          // 若allocation失败就不执行constructor
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    6. operator new的本质

      // 第二参数保证函数不抛出异常
      void *operator new(size_t size, const std::nothrow_t &){
          void *p;
          while((p=malloc(size)) == 0){// 如果内存耗尽导致分配失败 (实质调用malloc)
              _TRY_BEGIN
                  if(_callnewh(size) == 0)// 调用自定义函数进行处理
                      break;
              _CATCH(std::bad_alloc)
                  return 0;
              _CATCH_END
          }
          return p;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    7. delete的本质(析构–释放)

      delete pc;
      // 使用c语言进行翻译
      pc->~Complex();// 先析构
      operator delete(pc);// 后释放
      
      //operator delete源码
      void __cdecl operator delete(void *p)_THROW0(){
          free(p);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    8. 构造函数不能直接调用,析构函数可以

    9. 使用array new进行内存申请,必须使用array delete进行内存释放,如果不使用可能会导致内存泄漏

    10. debugger模式下,malloc实际申请内存

      • 申请内存首尾各4个字节的cookie
      • 32字节的debugger Header
      • 真正的申请的数据存储空间
      • 整体对齐到16的倍数的padding
    11. placement new:运行将对象建构在已分配的内存中

      #include 
      char *buf = new char[sizeof(Complex) * 3];
      Complex *pc = new(buf)Complex(1, 2);// buf是已分配的空间
      ···
      delete []buf; 
      
      
      // Complex *pc = new(buf)Complex(1, 2);的编译解释
      Complex *pc;
      try{
          void *mem = operator new(sizeof(Complex), buf);//直接返回buf,无作用
          pc = static_cast(mem);// 转型
          pc->Complex::Complex(1, 2);// 调用构造函数赋值
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    12. 没有placement delete,因为placement new没有分配内存

    13. 重载全局的 ::operator new/::operator delete

      // 更改全局的内存管理函数影响很大,尽量不用进行更改
      void* myAlloc(size_t size){
          return malloc(size);
      } 
      void myFree(void *ptr){
          return free(ptr);
      }
          
          
      inline void *operator new(size_t size){
          cout<< "global new()";
          return myAlloc(size);
      }
      
      inline void *operator new[](size_t size){
          cout << "global new[]()";
          return myAlloc(size);
      }
      
      inline void operator delete(void *ptr){
          cout<< "global delete()";
          myFree(ptr);
      }
      
      inline void operator delete[](void *ptr){
          cout<< "global delete[]()";
          myFree(ptr);	
      }
      
      • 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
    14. 重载类中的operator new / operator delete

      • 类成员operator new()可以重载多个版本,前提是每个版本必须有独特的参数列,其中第一参数必须是对象大小site_t
      Foo *p = new Foo;编译后的动作
      ···
      delete p;
      
      // Foo *p = new Foo;编译后的动作
      try{
          void *mem  = operator new(sizeof(Foo));// 申请内存
          p = static_cast(mem);// 指针类型转换
          p->Foo::Foo(1, 2);// 构造函数赋值
      }
      // delete p;编译后的动作
      p->~Foo();
      operator delete(p);
      
      // 类内重载
      class Foo{
          public:
          	void *operator new(size_t);
          	void operator delete(void*, size_t);// 第二参数可以不写
          	void *operator new(size_t size, void *start){
                  return start;
              }
          	void *operator new(size_t, long extra){
                  return malloc(size+extra);
              }
          	void *operator delete(void*, long){
                  cout<< "operator delete(void*, long)"<< endl;
              }
      }
      
      
      • 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
    15. 内存分配好后如果失败,必须要将内存进行回收。如果自定义的opereator new执行出错,会调用相应的析构函数

    第二讲 std::allocator

    1. 内存管理的核心目标就是提高速度、降低空间使用

    2. 内存池是由固定大小数组组成,这些数组通过指针形成链表,可能分散在不同的内存区域

    3. 使用单链表模拟allocator

      // 表示内存管理是一块池塘,使用链表进行管理
      #include 
      #include 
      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;
      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是空的,所以申请一大块
              size_t chunk = screenChunk * size;
              FreeStore = p = reinterpret_cast(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){
          // 将deleted object 插回free list前端 
          (static_cast(p))->next = freeStore;
          freeStore = static_cast(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
    4. union同一个数据使用不同的形式进行展示

    5. 加入embedded pointer 节省next指针的开销,实现更高性能的内存池

      /*
      嵌入式指针工作原理:空闲内存块需要使用指针进行链接,一旦分配出去,这一部分即可被再利用,从而节省空间(32位系统4个字节,即内存块要大于4个字节)
      */
      class Airplane {
      private:
      	struct AirplaneRep {
      		unsigned long miles;
      		char type;
      	};
      private:
      	union {
      		AirplaneRep 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都可以,编译器会给我们加上,因为类对象未构造,其行为未知。
      	static void* operator new(size_t size);    
      	static void operator delete(void* deadObj, 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){
      	// 继承会导致size不等,这里不做过多考虑
      	if (size != sizeof(Airplane))
      		return ::operator new(size);
      
      	Airplane* p = headOfFreeList;
      	if (p)
      		headOfFreeList = p->next;
      	else {
      		Airplane* newBlock = static_cast
      		(::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* deadObj, size_t size) {
      	if (deadObj == 0) return;
      	if (size != sizeof(Airplane)) {
      		::operator delete(deadObj);
      		return;
      	}
      	//回收该对象,指针的next指向空闲链表头, 然后调整空闲链表头部为deadObj
      	(static_cast(deadObj))->next = headOfFreeList;
      	headOfFreeList = static_cast(deadObj);
      }
      
      
      • 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
      • 61
      • 62
      • 63
      • 64
      • 65
    6. 抽象为allocator

      class myAllocator
      {
      private:
      	struct obj {
      		struct obj* next;  //embedded pointer
      	};
      public:
      	void* allocate(size_t);
      	void  deallocate(void*, size_t);
      	//void  check();
      
      private:
      	obj* freeStore = nullptr;
      	const int CHUNK = 5; //小一點方便觀察 
      };
      
      void* myAllocator::allocate(size_t size)
      {
      	obj* p;
      
      	if (!freeStore) {
      		//linked list 是空的,所以攫取一大塊 memory
      		size_t chunk = CHUNK * size;
      		freeStore = p = (obj*)malloc(chunk);
      
      		//cout << "empty. malloc: " << chunk << "  " << p << endl;
      
      		//將分配得來的一大塊當做 linked list 般小塊小塊串接起來
      		for (int i = 0; i < (CHUNK - 1); ++i) {  //沒寫很漂亮, 不是重點無所謂.  
      			p->next = (obj*)((char*)p + size);
      			p = p->next;
      		}
      		p->next = nullptr;  //last       
      	}
      	p = freeStore;
      	freeStore = freeStore->next;
      
      	//cout << "p= " << p << "  freeStore= " << freeStore << endl;
      
      	return p;
      }
      void myAllocator::deallocate(void* p, size_t)
      {
      	//將 deleted object 收回插入 free list 前端
      	((obj*)p)->next = freeStore;
      	freeStore = (obj*)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
    7. 类内对象或函数专门为该类服务,则使用static修饰。静态对象要在类外进行定义。

    8. 当operator new没能力为你分配申请的内存,会抛出一个std::bad_alloc exception。抛出异常之前会调用一个可以由用户指定的handler

      // 分配失败的补救措施
      typedef void*(new_handler)();
      new_handler set_new_handler(new_handler p)throw();
      
      • 1
      • 2
      • 3
    9. 两个新关键字

      • =default:使用该函数的默认版本(拷贝构造函数、拷贝赋值函数、析构函数)
      • =delete:不允许适用该函数
      class Foo{
      public:
      	Foo() =default;
          Foo(const Foo&) =delete;
          Foo& operator=(const Foo&) =delete;
          ~Foo() =default;
      ···
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    10. VC6标准库,其std::allocator的实现,只是以::operator new和::operator delete完成alloccate()和deallocate(),没有其他设计

      #ifndef _FARQ
      #define _FARQ
      #define _PDFT ptrdiff_t
      #define _SIZE size_t
      #endif
      
      template
      class allocator{
      public:
          typedef _SIZE size_type;
          typedef _PDFT difference_type;
          typedef _Ty _FARQ *pointer;
          typedef _Ty value_type;
          pointer allocate(size_type _N, const void *){
              return (_Allocate((difference_type)_N, (pointer)0));
          }
          void deallocate(void _FARQ *_P, size_type){
              operator delete(_P);
          }  
      };
      // _Allocate的定义
      template inline
      _Ty _FARQ *_Allocate(_PDFT _N, _Ty _FARQ*){
          if(_N < 0)
              _N = 0;
          return ((_Ty _FARQ*)operator new((_SIZE)_N *sizeof(_Ty)));
      }
      
      • 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

    第三讲 loki::allocator

    1. GCC的内存管理全部掌握在分配器手中,Loki可以将回收的内存归还操作系统

    2. 内存管理核心数据结构——三个指针=内存数据块首部 + 未分配数据块首部 + 内存数据块尾部

    3. 单向链表头插法可用于最高优先权的操作

    4. &*i,其中 i 是迭代器,表示取其指向元素的首地址

    5. VicinityFind方法

      for(;;){
      	if(向上未到尽头)
              顺序查找;
          if(向下未到尽头)
              顺向查找;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    6. Loki allocator特点

      • 精简强悍,手段暴力
      • 使用array取代list,使用index取代pointer
      • 可以很简单的判断chunk是否全回收,即可以将memory归还给操作系统
      • 有Defferring(暂缓归还)的能力
      • 是一个用来分配小块并无cookie的内存块的分配器
    7. 基本内存分配:当容器需要内存就调用operator new进行分配,当容器释放内存就调用operator delete。相比内存池,分配内存速度慢但是可以有效利用系统资源

    8. 不直接调用malloc,而使用operator new的原因是:虽然malloc速度快,但是不可以重载,失去了灵活性

    9. 大块内存的使用可以避免小内存块的cookie,增加内存空间的利用率

    10. 分配器追求的都是充分利用系统资源,eg:减少使用cookie

    11. vector的本质是三个指针

      • start:指向已使用的内存空间首地址
      • finish:指向已使用的内存空间的末地址
      • end_of_storage:总分配的内存空间末尾
    12. 17minp54

  • 相关阅读:
    缺少比较器,运放来救场!(运放当做比较器电路记录)
    弘辽科技:拼多多上下架会不会影响到流量?时间如何安排?
    Java Scanner类:用户输入
    golang 解决invalid argument tmp (type interface {}) for len
    玩转NFT夏季:这份工具宝典值得收藏
    地平线开发板配置网段
    【408考点之数据结构】二叉树的概念与实现
    金融市场,资产管理与投资基金
    KEIL 5.38的ARM-CM3/4 ARM汇编设计学习笔记10 - STM32的SDIO学习2
    UEditorPlus v2.7.0发布 开放独立文档,附件样式优化
  • 原文地址:https://blog.csdn.net/qq_43840665/article/details/125917555