cpp内存管理

cpp内存管理——侯捷

第一讲 primitives

  1. DOS 下面只能看到640K。
  2. 一般不会通过分配器直接拿内存,因为还内存的时候,需要记住一共分配了多少内存,这很不方便。
  3. new是一个表达式,编译器转换代码,第一步先分配内存,第二步转型,最后调用构造函数。最后使用的就是malloc。
  4. delete先析构函数,然后释放内存。最后使用的就是free
  5. 数组的内存分配和回收
    • new,Complex* pca = new Complex[3],唤醒三次构造函数
    • delete, delete [] pca; 唤醒三次析构函数。
  6. placement new, 允许我们将object构建在已经分配的内存中, 没有所谓的placement delete, 因为它根本就没有分配内存。
    1
    2
    char* buf = new char[sizeof(Complex) * 3];
    Complex * pc = new(buf)Complex(1, 2);
  7. 关于内存分配有三种,一种是new,一种是array new, 一种是placement new;
  8. new和delete是表达式,这种表达是不能被重载的。成员函数operator new和operator delete是可以被重载的,一般不会重载全局的operator new和operator delete。
  9. 为什么operator new和operator delete一定要是静态static的,因为调用这两个函数的时候,都是在创建或销毁对象的时候,这个时候也没有办法通过对象来调用一般的函数。当然,这两个函数一定是静态的,c++就不写static了。
  10. 降低malloc的次数,降低cookie的用量。将需要的内存一次一次性拿到,再分割为一块一块的,就可以减少malloc和cookie,这就是一种很小的内存池。
  11. union可以理解为是一个东西理解为不同的角度。
  12. 我们在一个类中对operator new和operator delete进行重写,在一定程度上就可以实现一个类的内存分配,但是这种办法,每一个类都需要写这样的内存分配,不符合软件工程的写法,所以我们把分配内存的步骤抽象出来,就变成了一种分配器。
  13. 当然,你在调用这个分配器的时候,它的调用动作都没有涉及到具体的函数名,所以,你可以自己定义一个宏,调用的时候,直接使用宏即可。当然MFC就是使用了这种方法。
  14. =default, =delete, c++2.0才有的关键字,一个是使用默认版本,一个是不要这个函数。拷贝构造,拷贝赋值和析构函数是由默认版本的。

第二讲 std::allocator

  1. 不同的编译器里面的内存分配可能都不一样。
  2. vc6+和BC5以及gnu2.9 的allocator只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊的设计。
  3. 在使用c++写代码的时候,其实已经不需要你自己设计数据结构了,那些经典的算法已经结合进了容器之中,但是容器分配的时候会有很多的cookie,所以,现在的想法是想办法使用分配器减少cookie的分配。因为容器中的所有的元素的大小是一样的,所以给去除cookie提供了可能。
  4. 但是GNU2.9使用了名为alloc的分配器,这是非常好的内存分配器。当然,这不是标准的分配器。
  5. 容器是不知道自己有没有带cookie的,因为一旦大小超过128字节,容器就直接开始调用malloc。
  6. 嵌入式指针,就是借用别人的前四个字节作为指针。当然如果对象小于4个字节,就没有办法借用指针了。
  7. 分配器的客户是容器,用户如果直接使用分配器需要知道你分配了多大,这是不方便的。容器可以自动记住容器的大小。
  8. 从战备池里面切的数量永远在1~20之间。两个指针特定的一指,就可以当做战备池了。每一次就先去战备池看一看有没有剩余的,如果没有就去maloc申请a*20*2 + roundup, 其中a*20用来进行分配,剩下的留作战备,roundup等于已经分配的大小除以16,所以就是越分配,内存的大小越大。
  9. 当分配的大小已经到了系统的最大内存的时候,就把已经分配的但是还没有使用的,最接近当前所要的大小的地方分配给战备池。当然如果没有最大的已经分配的时候,分配就停止了。这个时候实际如果还有很多小于当前想分配的大小,但是也不能将这些小的内存进行合并。
  10. 在进行判断if(a == 0)的时候如果两个等号写成了一个等号即if(a = 0),这个时候编译会通过,这个问题很难查出来,推荐写成if(0 == a),这个时候如果写成了一个等号,编译不会通过,有点意思。

第三讲 malloc/free

  1. malloc其实是很精细的。一个page可以看做是4k,一个paragraph可以看做是16k。
  2. 回收的时候,前后两个cookie的最后一位变成0,然后将前面变成嵌入式指针指回去就好了。
  3. 为什么要上下两个cookie,就是为了还内存的时候,可以看到上面和下面的内存的大小。
  4. vc6的内存系统的管理核心就是分段管理。分段管理分成了小段之后,就可以回收一部分之后,就归还给操作系统了。
  5. c++的标准库有一套内存的分配,下面的new, operator new也有一套,再下面的malloc也是由一套的,这样做有必要吗?是由必要的,因为你不知道下面是不是提供了相关的机制,你不能想着去依赖下面的实现。
  6. 一般来说,你是不需要自己写一个分配器的。

第四讲 loki::allocator

  1. 这节讲的是loki库里面的分配器,作者是modern c++的作者
  2. 优缺点
    • 曾有两个bug
    • 精简强悍,手段暴力
    • 使用array取代list,以index取代pointer的特殊手法
    • 能够以很简单的方式判断chunk全回收进而将memory归还给操作系统
    • 有延迟归还的能力
    • 这是个allocator,但是它的客户是容器,但是它的本身却使用vector,它使用容器但是背后的分配器使用的是标准库的那种容器。

第五讲 other issues

  1. c++提供了三项综合测试用以完成c++分配器之间的速度
    • insertion
    • 多线程环境中的insertion和erasure
    • 一个线程的生产者/消费者模式
  2. 使用bitmap_allocator的时候,如果没有全回收,则分配规模会成倍的递增,每一次全回收会造成下一次的分配规模减半。
  3. gnu c有7个给的分配器。