C++基础——内存管理篇

内存管理是计算机编程最为基本的领域之一。在很多脚本语言中,您不必担心内存是如何管理的,这并不能使得内存管理的重要性有一点点降低。对实际编程来说,理解您的内存管理器的能力与 局限性至关重要。在大部分系统语言中,比如 C 和 C++,您必须进行内存管理。本文将介绍手工的、 半手工的以及自动的内存管理实践的基本概念。
IBM 内存管理介绍

涉及到的内容包括

  • C API malloc free , malloc(分配器执行内存分配的过程)
  • 内存的回收(GC机制)
  • C++ 智能指针 实现的对象栈内存释放回收堆内存
  • 内存泄漏 判断和防止
  • 半自动内存管理 引用计数(referCount)
  • 内存池(pool) STL内存池实现

内存池的目的

  • 通过 new 表达式动态分配一个对象时,会调用 operator new 进行内存分配,这一步是直接和操作系统打交道的, 操作系统可能需要经过相对繁琐的过程才能将一块指向空闲内存的指针返回给用户, 所以这也是 new 比较耗时的一部分, 而第二步就是使用构造函数进行初始化。既然内存分配耗时, 那我们很容易想到的就是一次性分配一大块内存, 然后在用户需要的时候再划分其中一部分给用户, 这样的话, 一次分配, 多次使用, 自然而然提高了效率, 而用来管理这所谓的一大块内存的内存结构, 也就是今天我们要说的内存池。
  • 内存池带来的另外一个好处在于,频繁地使用new将导致系统内存空间碎片化严重, 容易导致的后果就是很难找到一块连续的大块内存, 空间利用率低,而内存池是一次性分配一大块内存空间,就缓解了内存碎片的问题。

第一级配置器

第一级配置器以 malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重新配置等操作,并且能在内存需求不被满足的时候,调用一个指定的函数

第二级配置器

第二级配置器维护着16个空闲链表(free list),各自管理大小分别为8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128 bytes的小内存块。
如果要分配的内存大于 128bytes,则移交给第一级配置器处理,直接用malloc。
如果要分配的内存小于 128bytes,则以内存池管理(memory pool),使用第二级配置器,找出适合的空闲链表, 从其上摘下一个节点将其头指针返回给用户,这就完成了对用户的内存分配。
释放过程则正好与分配相对应,如果用户分配的内存大于128bytes,直接用free,否则找出适当的空闲链表, 将指针所指的该段内存重新连接到空闲链表中
(注意此时并不把内存返回给操作系统, 如此可以重复利用)。

小内存块(空闲链表管理)–> 大内存块(malloc 向OS申请)

流程总结

使用 allocate 请求size大小的内存空间, 如果需要请求的内存大小大于128bytes, 直接使用malloc.
如果需要的内存大小小于128bytes, allocate根据size找到最适合的空闲链表.
a. 如果链表不为空, 返回第一个内存块, 链表头改为第二个内存块。
b. 如果链表为空, 使用refill为该空闲链表填充新的空间。
x. 如果内存池中有大于一个内存块的空间, 分配尽可能多的内存块(但是最多20个), 将一个内存块返回, 其他的内存块添加到链表中。
y. 如果内存池只有一个内存块的空间, 直接返回给用户.
z. 如果内存池连一个内存块的空间都没有, 再次向操作系统请求分配内存.
I 系统内存足够,分配成功,再次进行b过程
II 分配失败, 循环各个空闲链表, 寻找空间
A. 找到空间, 再次进行过程b
B. 找不到空间, 抛出异常
用户调用deallocate释放内存空间, 如果要求释放的内存空间大于128bytes, 直接调用free。
否则按照其大小找到合适的空闲链表, 并将其插入。
特点其实是这样的:

刚开始初始化内存池的时候, 其实内存池中并没有内存, 同时所有的空闲链表都为空链表.
只有用户第一次向内存池请求内存时, 内存池会依次执行上述过程的 1->2->b->z来完成内存池以及链表的首次填充, 而此时, 其他未使用链表仍然是空的

new 和 Malloc 的区别

  • malloc

malloc的全称是memory allocation,中文叫做:动态内存分配。
原型:extern void* malloc(unsigned int num_bytes);
说明:分配长度为num_bytes字节的内存块。如果分配成功,则返回指向被分类内存的指针;如果分配失败,则返回空指针NULL。(所以申请完内存需要判断所申请内存是否为空)当内存不再使用时,应使用free()函数讲内存块释放。
返回类型:void类型,表示通用变体类型指针,c,c++规定,void类型可以强制转换为任何其他类型的指针;void*指申请内存空间时,还不知道用户用这段空间来存储什么类型的数据。
释放:void free(void * FirstByte);讲malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存。

注意事项:
1)申请内存空间后,必须检查是否分配成功;
2)当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,以防后面的程序不小心使用了野指针
3)malloc和free应该配对使用;释放只能释放一次,若释放两次或更多会出现错误,(释放空指针例外,释放空指针其实等于啥都没做,释放空指针多少次都没有问题)
4)malloc从堆里面获得内存;函数返回的指针指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表,当操作系统收到程序的申请时,就会遍历链表,然后寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点分配给程序。

  • new

c++中,用new和delete动态创建和释放数组或者单个对象。
动态创建对象时,只需要指定其数据类型,而不必为该对象命名。
delete pi;//释放单个对象
delete [ ] pi;//释放数组

  • 两者区别
    1)new 返回指定类型指针,并且可以自动计算所需要的大小;malloc需要手动计算字节数,并且在返回后强制类型转换为实际类型的指针。
    (2)malloc只管分配内存,并不能对所得到的内存进行初始化,所以得到的一片新内存中,其值将是随机的;new不仅分配内存,还对内存中的对象进行初始化;free只管释放内存;delete不仅释放内存,还会调用对象的析构函数,销毁对象。
    (3)malloc/free是c++/c的标准库函数,头文件为stdlib.h;而new/delete是c++的运算符。他们都可用于申请动态内存和释放内存。

    (4)new/delete必须配对使用,malloc和free也一样

new 和 malloc 细微区别

  1. 申请的内存所在位置
    new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。
    那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。
    特别的,new甚至可以不为对象分配内存!
  2. 返回类型安全性
    new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
    类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图方法自己没被授权的内存区域。关于C++的类型安全性可说的又有很多了。
  3. 内存分配失败时候的返回值
    new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。

智能指针模板

template <typename T>
class smart_ptrs {
     

public:
    smart_ptrs(T*); //用普通指针初始化智能指针
    smart_ptrs(smart_ptrs&);

    T* operator->(); //自定义指针运算符
    T& operator*(); //自定义解引用运算符
    smart_ptrs& operator=(smart_ptrs&); //自定义赋值运算符
    
    ~smart_ptrs(); //自定义析构函数

private:
    int *count; //引用计数
    T *p; //智能指针底层保管的指针
};

template <typename T>
smart_ptrs<T>::smart_ptrs(T *p): count(new int(1)), p(p) {
     
}
//指针运算符
template <typename T>
 T* smart_ptrs<T>::operator->() {
     
    return p;
 }
 // 解引用
 template <typename T>
T& smart_ptrs<T>::operator*() {
     
    return *p;
}
//析构函数
template <typename T>
smart_ptrs<T>::~smart_ptrs() {
     
    if (--*count == 0) {
     
        delete count;
        delete p;
    }
}
//赋值运算符
template <typename T>
smart_ptrs<T>& smart_ptrs<T>::operator=(smart_ptrs& sp) {
     
    ++*sp.count;
    if (--*count == 0) {
      //自我赋值同样能保持正确
        delete count;
        delete p;
    }
    this->p = sp.p;
    this->count = sp.count;
    return *this;
}

你可能感兴趣的:(基础知识,C++,c++,内存管理)