首先,先谈谈我们为什么要做内存管理,每次使用malloc()它不香吗?
确实不香.
当我们malloc(sizeof(int))的时候,malloc真的只分配了 4 大小的字节吗?
给出一个在vc6下面malloc()后,分配内存的布局
当我们需要 4 个字节的时候,多出了这么多字段,我们得到的,就是指向int的指针
上下两个cookie 各占 4 字节
Debugger Header + no man land 共占 36个字节
一共为 48 个bytes. 也就是说 多出了 44 个bytes
每次malloc() 时,都会多出 44+ 个字节(因为有时会因为内存对齐为16 的整数倍而多加1-15个字节)
为什么我们不只malloc() 一块内存,然后有我们做管理分配使用,这样只 malloc() 一次,也就不会造成特别多的浪费.这就是做内存管理的原因.
当我们new了一个数组,但是我们delete 的时候却没加 [] ,此时会发生内存泄露???
这个先说明, 这个不一定会发生内存泄露.
#include <iostream>
class A
{
public:
A(){
};
private:
int i;
};
当我们申请一个数组时,如果这个类中没有涉及到动态分配过的内存,或者说这个类的析构函数没有写的必要(如 A 类 ).
此时当我们执行 A* apoint = new A[10]; 这条语句后,分配的内存空间图如下:
这个 cokkie 字段就记录了我们申请了 几个 空间,这里cookie 中就记录了 10 个元素.当我们delete时,即使不加 [],free 会向上访问,访问到cookie,它就会直到free几个元素,所以不会造成内存泄露
但是当我们的类中有动态分配的空间,如 string 类.
string* p = new string[3];
这里如果delete 的时候没有加 [] ,则只会调用 一次 析构函数,另外两个空间不会释放,造成内存泄露.
上面的测试前提是不写析构函数,如果写了析构函数,那么内存块的布局就不同了
如果写了析构函数,那么cookie 下面就会多一个字段,表明申请了几个元素,此时,free()向上访问,访问的就不是cookie,就会报错.
new 的具体行为
编译器执行这一行时, complex *pc = new complex(1,2); 会转化为
complex *pc;
try
{
void* mem = operator new(sizeof(complex)); //分配内存
pc = static_cast<complex*>(mem); //转化类型
pc->complex::complex(1,2); //调用构造函数
//注意这里调用的构造函数有点特殊,只有编译器可以通过类名,直接调用构造函数
}
catch(std::bad_alloc)
{
//抛出异常
}
我们再找到operator new 的源代码可以发现, operator new 里面也是调用了 malloc .
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
if (void* const block = malloc(size))
{
return block;
}
if (_callnewh(size) == 0)
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// The new handler was successful; try to allocate again...
}
}
当内存分配成功时,就return block,否则就是个死循环.
这里 解释 一下 _callnewh.
很明显,这里是分配内存失败(内存不足)才进入这个函数,进入这个函数释放内存,然后继续尝试分配.
这个c++ 提供了一个函数可以设置 --set_new_handler();
在分配内存错误之前,系统会抛出一个异常,但是在抛出异常之前,会重复调用_callnewh()
以上结论下面皆有测试.
测试如下:
#include <iostream>
#include <new>
#include <cassert>
using namespace std;
void noMoreMemory()
{
// cerr << "out of memory" << endl; 1
// abort(); 2
}
int main()
{
// set_new_handler(noMoreMemory); 3
int *p = new int[100000000000000000];
assert(p);
return 0;
}
测试一: 注释 3
结果:
测试结果表明 : 系统抛出异常
测试二: 注释 2
结果:
测试结果表明: 出现了死循环,系统在不断的调用我们set_new_handler()这个函数
测试三: 都不注释
测试结果表明: 调用abort();退出循环
接着上面谈:
编译器将这行代码 delete pc;
转化为:
pc->~complex(); //调用析构函数
operator delete(pc); //释放内存
void operator delete( void * p )
{
RTCCALLBACK(_RTC_Free_hook, (p, 0));
free( p );
}
从源码可以得出-- delete -> operator delete -> free