一、区别
- 类型安全性:
new/delete
是 C++ 中的操作符,它们能够调用对象的构造函数和析构函数,并且在动态分配内存时会自动计算所需的空间大小,因此是类型安全的。malloc/free
是 C 语言中的函数,它们只是分配和释放内存块,不会调用任何构造函数或析构函数,也不会进行类型检查。因此,在 C++ 中使用malloc/free
可能会导致内存泄漏或未定义的行为。
- 大小指定:
new
操作符根据指定的类型自动计算所需的内存大小,因此不需要显式指定分配的大小。malloc
函数需要显式指定要分配的内存块的大小,它接受一个参数来指定所需的字节数。
- 返回类型:
new
操作符返回一个指向动态分配的对象的指针,类型为所分配对象的类型。malloc
函数返回一个指向动态分配的内存块的指针,类型为void**
,需要进行显式的类型转换**。
- 构造和析构函数的调用:
new
操作符在分配内存后会调用对象的构造函数,用于初始化对象。delete
操作符在释放内存前会调用对象的析构函数,用于清理对象。malloc/free
不会调用任何构造或析构函数,它们只是分配和释放内存块。
- 异常处理:
new
操作符在分配失败时会抛出std::bad_alloc
异常。malloc
函数在分配失败时返回NULL
指针,需要手动检查分配是否成功。
1 | //动态分配了一个整数对象,并将其初始化为 5,然后将返回的指针赋值给指针变量 p1 |
二、new和delete的实现原理
分配内存
在使用
new
运算符来创建一个对象时,编译器会调用一个名为operator new
的函数来分配内存。这个函数负责从堆中分配足够大的、原始的、未命名的内存空间。然后,编译器运行相应的构造函数,为这段内存传入初始值。
对象被分配了空间并构造完成,返回一个指向该对象的指针。如果分配失败,
operator new
函数抛出std::bad_alloc
异常构造对象
分配内存后,编译器会调用对应的构造函数来初始化对象。这确保了对象的状态是有效的,并且任何必要的资源(如动态分配的内存或文件句柄)都已经被正确初始化。
- 返回指针
一旦对象被正确地构造,new
运算符将返回一个指向新分配的内存的指针。这个指针可以用于访问对象的成员变量和方法。
- 释放内存
当使用 delete
运算符来释放对象时,编译器会调用一个名为 operator delete
的函数。这个函数负责释放之前分配的内存,并且在必要时调用对象的析构函数来清理资源。
三、调用free会发生什么
- 标记内存块为可用: 调用
free
函数会将动态分配的内存块标记为可用。这意味着该内存块现在可以被系统重新分配给其他程序或进程使用。但是,该内存块的内容不会被清除或修改,因此在释放后的内存块中仍然可能包含之前存储的数据。 - 释放内存块: 被释放的内存块会被添加到系统的内存空闲列表中,以便以后的内存分配请求可以使用它。
- 合并相邻的空闲内存块: 在释放内存块后,系统可能会检查相邻的空闲内存块,并尝试将它们合并成一个更大的内存块。这有助于减少内存碎片化,并提高内存的利用率。
- 返回给操作系统(部分情况下): 在某些操作系统中,当大块内存被释放时,系统可能会将该内存块返回给操作系统,以便它可以被重新分配给其他程序或进程使用。但是,这并不是所有操作系统都会立即执行的操作,具体取决于操作系统的内存管理策略和实现。
悬空指针
当使用 delete
运算符释放动态分配的内存时,系统会回收该内存块,并将其标记为可用的空闲内存。然而,指向这块内存的指针本身并没有被修改,它仍然保持原来的值,但这个地址不再有效,这就是所谓的悬空指针。因此,为了避免使用悬空指针,通常将 ptr
设置为 nullptr
。
野指针
野指针是指向未知或无效内存地址的指针。野指针一般是未初始化的指针、已经被释放的指针或者指向无效内存的指针。
- 如果一个指针变量未被显式初始化,它将包含一个随机的值,这个值可能是一个无效的内存地址。当尝试访问这个指针所指向的内存时,就会产生未定义的行为。因此,为了防止出错,对于指针初始化时都是赋值为nullptr,这样在使用时编译器就不会直接报错,产生非法内存访问。
- 当一个动态分配的内存块被释放(如使用
delete
或free
),但之后仍然有指针指向这块内存,这些指向已经释放的内存的指针就称为野指针。 - 有时指针可能会指向已经被销毁或无效的对象,尝试访问这样的指针就会产生未定义的行为,这也属于野指针的范畴。