首页 > 技术文章 > new、delete和malloc、free的一点思考

cutelife 2021-05-24 11:31 原文

一、new与malloc,free与delete的异同

(1)相同点

都用于动态内存的申请、释放

(2)不同点

  • new与delete是C++的运算符,支持运算符重载,而malloc与free是C/C++的标准库函数,支持函数覆盖;
  • new能自动计算所需内存大小,malloc需要传入分配内存大小;
  • new申请成功返回的是指向该类型的指针,而malloc申请成功返回的void *的指针,所以我们一般调用malloc的形式为(int *)malloc(10*sizeof(int));
  • new和delete操作有两步,先调用operator new的标准库函数(里面包含了malloc),调用构造函数(如果有必要),delete反之,所以new、delete底层调用了malloc、free标准库函数;
  • new之后采用free语法上没有操作,大多时间也不会报错,但是会有意外情况,new基本数据类型与travial destructor的数组,这种情况free不报错,其他情况会报段错误(这里在第二节会详细测试);
  • delete调用一次析构函数、delete[]调用多个析构函数,取决于申请的空间的前4个字节(存放delete[] 调用析构函数的次数);
  • new、delete不需要库文件的支持,而后者需要库文件的支持;
  • new有三种方式,plain new,nothrow new,placement new;

(3)free为什么不需要指定大小

系统在分配内存时除了分配指定的内存空间外,还要分配用于保存内存空间大小等信息。所以内存释放时不再需要再指定释放多大的内存空间,只需要指定该块内存空间的首地址即可。

实质上,能在pp指针前12个字节标记出申请内存的大小,是依靠着struct malloc_chunk这个结构体。

具体测试

 

 在红框的位置,出现的是申请内存的大小(经过了多次测试)。

二、new、delete、malloc、free的一些测试

之前使用new和delete时发现的一些有趣事

 1 typedef char* p_char;
 2 #define d_char char *
 3 class T {
 4 public:
 5     int a;
 6     T() { a = 10; cout << "constructor" << endl; }
 7     ~T() {
 8         cout << "destructor" << endl; 
 9     }
10 };
11 
12 int main()
13 {
14     const int NUM = 3;
15     T* p1 = new T[NUM];
16     cout << hex << p1 << endl;      //输出P1的地址
17     delete p1;
18 }

这是我写的错误代码,一开始认为只会是内存泄漏,但是实际情况是段错误,输出为

 

 

以下是我的进行的思考

(1)malloc、free的测试

根据上述图片出现的情况,实际输出了一次析构函数后出现段错误,我将问题定位在delete的第二步,operator delete时出现错误,测试以下代码。

1 int* pp = (int *)malloc(10 * sizeof(int));
2     pp++;
3     free(pp);

发现也会报段错误;

推断free只能从申请的首地址进行释放内存,应该是和操作系统内存管理有关系,比如free是从free list中读取的内存信息,才能准确释放内存,所以上述代码出现段错误;

所以delete一个new的数组,从p1的位置调用析构函数没有错误,但是free时,没有找到正确的申请内存首地址(应该是p1-4),所以free失败,产生段错误;

(2)new、delete测试

这样测试完,后我将T类变成基本数据类型,发现没有异常;

1  int * a = new int[10];
2      delete a;

查阅、咨询别人,得出结论是,系统申请基本数据类型,并不会多申请4个字节的内存所以不会报段错误,多申请4个字节是为了记录class调用的析构函数次数;

然后又测试了以下代码

 1 class T {
 2 public:
 3     int a;
 4     T() { a = 10; cout << "constructor" << endl; }
 5     //~T() {
 6     //    cout << "destructor" << endl; 
 7     //}
 8 };
 9 
10 int main()
11 {
12     const int NUM = 3;
13     T* p1 = new T[NUM];
14     cout << hex << p1 << endl;      //输出P1的地址
15     delete p1;
16 }

惊奇的发现,也并没有报段错误;

查阅资料后,推断travial destructor的类应该不会调用析构函数,或者说被编译器优化掉了;

(3)得出结论

  • new一个类型的数据,如果不是基本数据类型、travial destructor,会多申请4个字节的内存大小,存放调用析构函数的次数;
  • malloc会在操作系统某一个位置记录申请内存大小,所以free才能准确的释放内存;
  • delete[]存在会使编译器获取数组大小(size)然后析构函数再被依次应用在每个元素上,一共size次。否则,只有一个元素被析构,无论那种情况,都会将new的内存全部返还给操作系统

最后感谢以下大佬的博客

https://blog.csdn.net/shandaliuyan/article/details/5930719

https://www.cnblogs.com/hezhixiong/p/4535534.html

感谢阿秀大佬

https://gitee.com/Rosasp/CPlusPlusThings

推荐阅读