如果有人拿这段话来总结new/delete,malloc/free,那这篇博客就不要看,很无聊的:
new、delete是操作符,malloc和free是库函数。
new/delete会调用operator new/delete 来开辟/释放内存,也会调用构造/析构函数来初始化/释放资源。
当然还有一句无聊的:由于malloc/free看不到构造/析构函数,所以不要混用new/delete,malloc/free。
如果提出malloc的实现在某些系统下是不同的,那些许值得一看。
来看几个有意思的问题:
1、
int main()
{
int *p = (int *)malloc(sizeof(int));
p = NULL ;
free(p);
//...
return 0 ;
}
C++保证释放0值的指针时安全的,所以free(NULL)和free(0),delete (void *)NULL是被允许的,但p的内存没有被释放。
2、
windows底下给malloc传递参数-1会崩溃:
int main()
{
int *p = (int *)malloc(-1);
free(p);
return 0 ;
}
而linux底下:
int main()
{
int *p = (int *)malloc(-1);
if( p == NULL )
cout << malloc_usable_size(p) << endl ;//命中且打印结果为0
return 0 ;
}
malloc在不同系统下的实现的确不一样,linux底下检查参数为负数并返回NULL,windows似乎没有负数检查机制,而是在分配内存时由于需求过大而抛出异常。 对了,windows下用_msize查看内存大小,linux下用malloc_usable_size。
3、再来一个例子
windows下:
int main()
{
int *p = (int *)malloc(0);
cout << _msize(p) << endl ;//结果为0
free(p);
return 0 ;
}
windows下如果给malloc传递0参数,这块地址返回不为空但不可写。记住不可写。
而linux底下呢:
int main()
{
int *p = (int *)malloc(0);
*p = 1 ;
cout << *p << endl ;
cout << malloc_usable_size(p) << endl ;//打印结果为12
return 0 ;
}
当你申请0时它也返回一个12字节大小的内存块给你使用,经测试,当你申请小于12字节的内存块时分配12字节的内存,然后每次以8字节增长。而且经测试,linux下的malloc进行了内存块的初始化0操作。
int main()
{
int *a = (int *)malloc(0);
int *b = (int *)malloc(12);
int *c = (int *)malloc(13);
int *d = (int *)malloc(20);
int *e = (int *)malloc(21);
int *f = (int *)malloc(28);
int *g = (int *)malloc(29);
int *h = (int *)malloc(36);
int *i = (int *)malloc(37);
int *j = (int *)malloc(44);
cout << malloc_usable_size(a) << endl ;//12
cout << malloc_usable_size(b) << endl ;//12
cout << malloc_usable_size(c) << endl ;//20
cout << malloc_usable_size(d) << endl ;//20
cout << malloc_usable_size(e) << endl ;//28
cout << malloc_usable_size(f) << endl ;//28
cout << malloc_usable_size(g) << endl ;//36
cout << malloc_usable_size(h) << endl ;//36
cout << malloc_usable_size(i) << endl ;//44
cout << malloc_usable_size(j) << endl ;//44
cout << a[0] << " " << a[1] << " " << a[2] << endl ;
return 0 ;
}
4、这份代码会发生什么不得了的事情:
#include <iostream>
using namespace std;
class Node
{
public:
Node(int val=2):data(val){}
~Node()
{
cout << "This is ~Node()" << endl ;
}
private:
int data;
};
int main()
{
Node *p = new Node[4];
delete p;
return 0 ;
}
这就是对象数组为什么必须用delete[]来释放的原因,使用new[]来创造对象数组时,系统会在开头多分配4个字节是用来保存数组个数,因为析构时每个对象都要调用一次析构函数。使用delete p,系统怎么知道如何调用这么多析构函数。而更严重的是由于delete[] p会正确释放动态开辟的内存地址,delete p不仅只释放一个p的内存而且忽略了4个字节上的地址偏移,抛出异常在所难免。
Node *p = new Node[5];
0098513D push 18h//多出4个字节
...
//delete[]
009851E3 call Node::`vector deleting destructor' (098126Ch)
//delete
008150DE call Node::`scalar deleting destructor' (08113F7h)
delete[]和delete不同的汇编调用。
综上,如果new[]创建对象数组,一定要用delete[]释放。
5、知识点:
默认的new在内存分配失败时不会返回NULL,而是抛出bad_alloc的异常。当然,系统也提供了能返回NULL的new版本。
int main()
{
int *p = new (nothrow) int[5]();
if( p == NULL )
exit(1);
delete[] p ;
return 0 ;
}
当然还有定位new:placement new:在已存在的内存上开辟一块内存,简单来说就是在已存在的内存上拿一块内存来使用。用的不多。
int main()
{
int *p = new int[1024]();
int *q = new (p) int[4];
int m[1024] ;
int *n = new (m) int[256];
return 0 ;
}
定位new就可以定堆上的空间,也可以使用栈上的空间。