标准类型字节
类型 字节 byte 1 bool 1 char 1 short 2 long 4 int 4 double 8 long long 8 指针 32位4 64位8
new
int *a = new int; 运算符 他是没办法改变的 他一定执行两件事情 一个是调用构造函数 一个是给指针分配内存 也就是调用operator new函数 他做了什么事情
void *p = A::operator new(sizeof(A));
A* a1 = static_cast<A*>(p);
a1->A::A();
return a1;
operator new函数 我们是可以重载的
class Node
{
public:
void* operator new(size_t t) { cout << "调用了operator new函数"<<endl;
void * temp = malloc(t);
return temp; };
int a;
char b;
Node() :a(1), b('b')
{
cout << "调用了构造函数"<<endl;
}
};
int main()
{
Node* node = new Node();
}

void * operator new(size)
是函数 不是运算符 返回的是一个void* 分为全局重载和类重载 系统默认的全局重载就只是调用了malloc A* a = new A;我们知道这里分为两步:1.分配内存,2.调用A()构造对象。事实上,分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),如果没有重载,就调用::operator new(size_t ) ::operator new(size_t )是全局的
系统中真正的内存分配
 系统中并不是你要分配多少内存就在系统中占用多少内存 如图 block size那块蓝色的区域是真实分配的大小 上面和下面都有填充0xfdfdfdfd用来分割客户可以使用的内存区和不可使用的内存区 同时,当这块内存被归还时,编辑器也可以通过下gap的值区判断当前内存块是否被越界使用了。 这也是为什么只能分配比用户申请更大的内存给用户 也就是用户申请10字节的 你必须给12字节的(为了对齐) 原因在于你如果给8字节的 虽然我们总的分配的内存更大 因为有cookie啊 或者debug什么的 但是 如果你少给了 例如申请10字节 你给8字节 那么多的两个字节数据就被写在了0xfdfdfdfd这里的前两个fd被覆盖了 那么系统检测到只有两个fd就会直接崩溃
最上面和最下面都有一个cookie占4个字节 这个cookie会记录这块内存总共用了多少 这cookie作用是在释放的时候知道要释放到哪个Header链表末尾去(Header应该就是一个内存池) debug header那块区域是调试信息存储的地方 这也是debug版本的程序为什么会比release版本大很多的原因 并且总大小要为16的倍数
小内存分配(内存池)
就会用上内存池 主要也是为了减少cookie等空间白白消耗 在进入程序之前 系统就会分配一个堆空间 这个堆空间用于管理的动态分配 他会创建一个长度为16的类型为HEADER的链表 这个链表的每一个结点都会管理一个1MB的空间 我的感觉就是 如果这个空间被申请出去了 那么就为1 没有就为0 free和delete不需要提供大小参数的原因在于他只需要将这个链表相应结点置为1就行了 不需要知道要释放多大内存 释放的时候通过cookie就知道释放的这个内存是多大 要放到哪个header链表中去 
 这个就是Header链表的样子 16下面挂着的就是 最开始如果16下面没有挂着内存块 系统就会分配一块很大的内存然后分割成很多个16字节的内存块挂在16下面 如果有人要申请8-16字节的内存 就从这拿
内存池同样会产生碎片
主要原因是 例如如果我们在8字节的内存块都用完而16字节的内存块还有的时候 我们再申请8字节内存 并不会找系统要一块很大的内存再重新分割 而是找一个最适合他的 例如16字节的 分割8字节给用户 然后剩下8字节就重新挂到8字节下面 不能挂回16字节下面 如果挂回去 下次要申请16字节的时候 就会出问题 那么时间长了 就会小的字节链表下面挂了很多被分割的内存块 然后大的字节链表字节块都被分割了 但是实际上用户又把这些内存块归还了  像这样
具体看这篇博客
|