0%

new/delete和malloc/free

一、区别

  1. 类型安全性:
    • new/delete 是 C++ 中的操作符,它们能够调用对象的构造函数和析构函数,并且在动态分配内存时会自动计算所需的空间大小,因此是类型安全的。
    • malloc/free 是 C 语言中的函数,它们只是分配和释放内存块,不会调用任何构造函数或析构函数,也不会进行类型检查。因此,在 C++ 中使用 malloc/free 可能会导致内存泄漏或未定义的行为。
  2. 大小指定:
    • new 操作符根据指定的类型自动计算所需的内存大小,因此不需要显式指定分配的大小。
    • malloc 函数需要显式指定要分配的内存块的大小,它接受一个参数来指定所需的字节数。
  3. 返回类型:
    • new 操作符返回一个指向动态分配的对象的指针,类型为所分配对象的类型
    • malloc 函数返回一个指向动态分配的内存块的指针,类型为 void**需要进行显式的类型转换**。
  4. 构造和析构函数的调用:
    • new 操作符在分配内存后会调用对象的构造函数,用于初始化对象。
    • delete 操作符在释放内存前会调用对象的析构函数,用于清理对象。
    • malloc/free 不会调用任何构造或析构函数,它们只是分配和释放内存块。
  5. 异常处理:
    • new 操作符在分配失败时会抛出 std::bad_alloc 异常。
    • malloc 函数在分配失败时返回 NULL 指针,需要手动检查分配是否成功。
1
2
3
4
5
6
7
//动态分配了一个整数对象,并将其初始化为 5,然后将返回的指针赋值给指针变量 p1
int* p1 = new int(5);
delete p1;

//动态分配了一个大小为 sizeof(int) 字节的内存块,并将返回的指针强制转换为整型指针类型,然后将其赋值给指针变量 p2
int* p2 = (int*)malloc(sizeof(int));
free p2;

二、new和delete的实现原理

  1. 分配内存

  2. 在使用 new 运算符来创建一个对象时,编译器会调用一个名为 operator new 的函数来分配内存。这个函数负责从堆中分配足够大的、原始的、未命名的内存空间。

  3. 然后,编译器运行相应的构造函数,为这段内存传入初始值。

  4. 对象被分配了空间并构造完成,返回一个指向该对象的指针。如果分配失败,operator new 函数抛出 std::bad_alloc 异常

  5. 构造对象

​ 分配内存后,编译器会调用对应的构造函数来初始化对象。这确保了对象的状态是有效的,并且任何必要的资源(如动态分配的内存或文件句柄)都已经被正确初始化。

  1. 返回指针

​ 一旦对象被正确地构造,new 运算符将返回一个指向新分配的内存的指针。这个指针可以用于访问对象的成员变量和方法。

  1. 释放内存

​ 当使用 delete 运算符来释放对象时,编译器会调用一个名为 operator delete 的函数。这个函数负责释放之前分配的内存,并且在必要时调用对象的析构函数来清理资源。

三、调用free会发生什么

  1. 标记内存块为可用: 调用 free 函数会将动态分配的内存块标记为可用。这意味着该内存块现在可以被系统重新分配给其他程序或进程使用。但是,该内存块的内容不会被清除或修改,因此在释放后的内存块中仍然可能包含之前存储的数据。
  2. 释放内存块: 被释放的内存块会被添加到系统的内存空闲列表中,以便以后的内存分配请求可以使用它。
  3. 合并相邻的空闲内存块: 在释放内存块后,系统可能会检查相邻的空闲内存块,并尝试将它们合并成一个更大的内存块。这有助于减少内存碎片化,并提高内存的利用率。
  4. 返回给操作系统(部分情况下): 在某些操作系统中,当大块内存被释放时,系统可能会将该内存块返回给操作系统,以便它可以被重新分配给其他程序或进程使用。但是,这并不是所有操作系统都会立即执行的操作,具体取决于操作系统的内存管理策略和实现。
悬空指针

当使用 delete 运算符释放动态分配的内存时,系统会回收该内存块,并将其标记为可用的空闲内存。然而,指向这块内存的指针本身并没有被修改,它仍然保持原来的值,但这个地址不再有效,这就是所谓的悬空指针。因此,为了避免使用悬空指针,通常将 ptr 设置为 nullptr

野指针

野指针是指向未知或无效内存地址的指针。野指针一般是未初始化的指针、已经被释放的指针或者指向无效内存的指针

  • 如果一个指针变量未被显式初始化,它将包含一个随机的值,这个值可能是一个无效的内存地址。当尝试访问这个指针所指向的内存时,就会产生未定义的行为。因此,为了防止出错,对于指针初始化时都是赋值为nullptr,这样在使用时编译器就不会直接报错,产生非法内存访问。
  • 当一个动态分配的内存块被释放(如使用 deletefree),但之后仍然有指针指向这块内存,这些指向已经释放的内存的指针就称为野指针。
  • 有时指针可能会指向已经被销毁或无效的对象,尝试访问这样的指针就会产生未定义的行为,这也属于野指针的范畴。