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

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

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

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

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

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

    #include <new>
    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<Complex*>(mem);// 转型
        pc->Complex::Complex(1, 2);// 调用构造函数赋值
    }
    
  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);	
    }
    
  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<Foo*>(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;
            }
    }
    
    
  15. 内存分配好后如果失败,必须要将内存进行回收。如果自定义的opereator new执行出错,会调用相应的析构函数

第二讲 std::allocator

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

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

  3. 使用单链表模拟allocator

    // 表示内存管理是一块池塘,使用链表进行管理
    #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;
    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<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){
        // 将deleted object 插回free list前端 
        (static_cast<Screen*>(p))->next = freeStore;
        freeStore = static_cast<Screen*>(p);
    }
    
  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<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* deadObj, size_t size) {
    	if (deadObj == 0) return;
    	if (size != sizeof(Airplane)) {
    		::operator delete(deadObj);
    		return;
    	}
    	//回收该对象,指针的next指向空闲链表头, 然后调整空闲链表头部为deadObj
    	(static_cast<Airplane*>(deadObj))->next = headOfFreeList;
    	headOfFreeList = static_cast<Airplane*>(deadObj);
    }
    
    
  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;
    }
    
    
  7. 类内对象或函数专门为该类服务,则使用static修饰。静态对象要在类外进行定义。

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

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

    • =default:使用该函数的默认版本(拷贝构造函数、拷贝赋值函数、析构函数)
    • =delete:不允许适用该函数
    class Foo{
    public:
    	Foo() =default;
        Foo(const Foo&) =delete;
        Foo& operator=(const Foo&) =delete;
        ~Foo() =default;
    ···
    };
    
  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 _Ty>
    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<class _Ty> inline
    _Ty _FARQ *_Allocate(_PDFT _N, _Ty _FARQ*){
        if(_N < 0)
            _N = 0;
        return ((_Ty _FARQ*)operator new((_SIZE)_N *sizeof(_Ty)));
    }
    

第三讲 loki::allocator

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

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

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

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

  5. VicinityFind方法

    for(;;){
    	if(向上未到尽头)
            顺序查找;
        if(向下未到尽头)
            顺向查找;
    }
    
  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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逆羽飘扬

如果有用,请支持一下。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值