C++:new与delete

hello,各位小伙伴,本篇文章跟大家一起学习《C++:new与delete》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !

🚀 C++内存管理

我们学过malloc、calloc、realloc、free,这些都是我们在C语言时学的,那么C++又引入了什么呢?

✈️ 初识newdelete

没错,就是newdelete,他们都是用于动态内存管理的操作符。

  • new 用于在堆内存中动态地分配内存空间,通常用于创建对象或数组。
  • delete 用于释放由 new 分配的内存空间,以防止内存泄漏。

以下是它们的基本用法:

// 使用 new 分配单个对象的内存空间
int* ptr = new int;

// 使用 new 分配数组的内存空间
int* arr = new int[10];

// 使用 delete 释放单个对象的内存空间
delete ptr;

// 使用 delete[] 释放数组的内存空间
delete[] arr;

🔥在使用 new 分配内存后,必须使用 deletedelete[] 来释放这些内存,否则会导致内存泄漏。

当然,我们还可以在new时进行初始化,如:

int* ptr = new int(1);
int* arr = new int[10]{1,2,3,4,5,6,7,8,9,0};

但是对于数组,我们一般用循环初始化,毕竟数组的元素个数可多可少

🔥同样是在堆上进行操作,那么newdelete相比于mallocfree又有什么优势呢?

  1. 构造函数和析构函数的调用

    • 使用 new 分配的内存会调用对象的构造函数,而释放内存时会调用对象的析构函数。
    • 使用 malloc() 分配的内存不会调用对象的构造函数和析构函数,它仅分配内存空间。
    • 因此,如果你在 C++ 中使用类,尤其是含有构造函数和析构函数的类,应该优先使用 newdelete,以确保对象的生命周期正确管理。
  2. 类型安全

    • newdelete 是类型安全的,它们会自动为分配和释放的内存调用正确的构造函数和析构函数。
    • malloc()free() 不是类型安全的,它们仅仅操作指针和内存地址,不关心数据类型。
  3. 数组分配

    • new[]delete[] 用于分配和释放数组,它们可以正确处理数组的元素。
    • malloc()free() 无法直接处理数组,你需要手动跟踪分配的内存大小,并确保正确释放。
  4. 内存对齐

    • newnew[] 会保证分配的内存按照对象的对齐要求进行,而 malloc() 则不保证内存对齐。
    • 内存对齐在某些情况下可能会影响性能和内存访问的正确性。

总的来说,在 C++ 中,如果你在使用类或动态分配数组,最好使用 newdelete,因为它们更符合 C++ 的对象模型,并且提供了更多的类型安全和便利性。

还有

  • new不需要强制类型转换
  • new不需要计算需要多大内存空间

✈️ newdelete底层逻辑

🔥 operator newoperator delete函数

newdelete是用户进行动态内存申请和释放的操作符,operator new operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

看下述代码:

class A
{
public:
	A(int a = 1)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* ptr = (A*)operator new(sizeof(A));

	new(ptr)A;// 调用构造函数,不能直接调用
	ptr->~A();// 调用析构函数,能直接调用

	return 0;
}

operator new该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常
operator delete该函数最终是通过free来释放空间的。

new(ptr)A;// 调用构造函数,不能直接调用
ptr->~A();// 调用析构函数,能直接调用

由于operator delete不会调用构造函数和析构函数,要手动操作。
调用构造函数时也可以传参:

new(ptr)A(1);

✈️ 数组new[]delete[]注意事项

🔥对于delete[]delete一定要对应使用
对于动态分配的内存,在释放时必须使用与分配时相对应的操作符。

  • 对于使用new分配的单个对象,应使用delete释放内存。
  • 对于使用new[]分配的数组,应使用delete[]释放内存。

使用delete释放使用new[]分配的内存或使用delete[]释放使用new分配的内存都会导致未定义的行为。这是因为newnew[]分别调用了不同的构造函数,因此释放时必须相应地使用deletedelete[]来调用相应的析构函数。

例如,如果你这样分配内存:

int* a= new int;
int* arr= new int[10];

则应该这样释放内存:

delete a;
delete[] arr;

这样做可以确保内存被正确释放,避免内存泄漏和其他潜在的问题。

🔥对于自定义类型,还有一些要注意的点
我们来看下述代码1:

class A
{
public:
	A(int a = 1)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

int main()
{
	A* ptr = new A[10];

	return 0;
}

大家猜猜系统为开辟的数组一共用了多大的内存(32位机器)
答案是:44字节
在这里插入图片描述
我们再来看看这个代码2:

int main()
{
	int* arr = new int[10];
	return 0;
}

大家再猜猜系统为开辟的数组一共用了多大的内存(32位机器)
答案是:40字节
在这里插入图片描述

这就奇怪了呀,明明都是开10个int类型的数组,为什么?

这是因为自定义类型中有析构函数,那么编译器会认为这个析构函数是需要使用的,但是要析构多少次,就要多开4个字节(32位机器地址大小为4个字节,64位地址大小为8字节)来存储有多少个元素需要进行析构,如图:
在这里插入图片描述

那么如果该自定义类型没有析构函数,是不是就变成40字节了?
答案是:正确的

在这里插入图片描述

✈️ newdelete的实现原理

🔥对于内置类型:
如果申请的是内置类型的空间,newmallocdeletefree基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]delete[]申请的是连续空间,而且new在申请空间失败时会抛异常malloc会返回NULL

🔥对于自定义类型:

  • new的原理
  1. 调用operator new函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造在这里插入图片描述
  • delete的原理
  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete函数释放对象的空间在这里插入图片描述
  • new T[N]的原理
  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
  2. 在申请的空间上执行N次构造函数在这里插入图片描述
  • delete[]的原理
  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间在这里插入图片描述

✈️ 定位new表达式(placement-new) (现阶段了解一下即可)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

  • 使用格式:
    new (place_address) type或者new (place_address) type(initializer-list)
    place_address必须是一个指针,initializer-list是类型的初始化列表
  • 使用场景:
    定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
class A
{
public:
	A(int a = 1)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* ptr = (A*)malloc(sizeof(A));
	new(ptr)A(10);// 注意:如果A类的构造函数有参数时,此处需要传参,如我所写
	free(ptr);
	
	A* ptr = (A*)operator new(sizeof(A));
	new(ptr)A(10);
	operator delete (ptr);
	
	return 0;
}

✈️ malloc/free和new/delete的区别

🔥malloc/freenew/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:

  1. mallocfree函数newdelete操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

✈️ 关于内存泄漏

1.🔥内存泄漏的危害!!!

内存泄漏是指程序在动态分配内存后,无法再次访问或释放该内存,导致程序持续占用内存而不释放。内存泄漏可能会导致以下危害:

  1. 内存资源浪费:内存泄漏会导致程序持续占用内存而不释放,随着时间的推移,系统的可用内存将逐渐减少,可能导致系统性能下降甚至崩溃。

  2. 程序性能下降:随着内存泄漏的累积,系统可用内存减少,可能会导致频繁的内存交换(如果系统使用虚拟内存),增加了页面调度的开销,降低了程序的整体性能。

  3. 程序崩溃:当程序持续占用内存而不释放,最终可能导致系统内存耗尽,触发操作系统的内存管理机制,导致程序崩溃或被系统强制终止。

  4. 资源管理混乱:内存泄漏可能会导致程序中的资源管理混乱,例如无法及时释放打开的文件、数据库连接或网络连接,进而影响系统的稳定性和可靠性。

  5. 难以调试和定位问题:内存泄漏通常是程序中较为隐蔽的问题,随着内存使用的增加,程序的运行速度可能会变慢,但不一定会立即导致崩溃或错误,因此难以定位和调试。

为了避免内存泄漏的危害,程序员应该养成良好的内存管理习惯,及时释放不再需要的内存,并使用内存检测工具(如Valgrind、AddressSanitizer等)来帮助检测和修复潜在的内存泄漏问题。

小伙伴们一定要记得噢!!!

2.🔥内存泄漏的分类(了解一下即可)

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak

  • 系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统
资源的浪费,严重可导致系统效能减少,系统执行不稳定。

3.🔥如何检测内存泄漏(了解一下)

在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,该函数只报出
了大概泄漏了多少个字节,没有其他更准确的位置信息。

int main()
{
	int* p = new int[10];

	// 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏
	_CrtDumpMemoryLeaks();
	return 0;
}


// 程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置
Detected memory leaks!
Dumping objects ->
{79} normal block at 0x00EC5FB8, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.

咱目前只是了解一下,无需理解

因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的
可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内
存泄漏检测工具处理的。

好啦,这篇文章就到此结束了
所以你学会了吗?

好啦,本章对于《C++:new与delete》的学习就先到这里,如果有什么问题,还请指教指教,希望本篇文章能够对你有所帮助,我们下一篇见!!!

如你喜欢,点点赞就是对我的支持,感谢感谢!!!

请添加图片描述

相关推荐

  1. C++:newdelete

    2024-04-23 20:18:02       36 阅读
  2. c++的newdelete

    2024-04-23 20:18:02       58 阅读
  3. MySQL:drop、deletetruncate区别

    2024-04-23 20:18:02       53 阅读
  4. c++中的newdelete

    2024-04-23 20:18:02       69 阅读
  5. 【SQL】delete truncate 命令的区别

    2024-04-23 20:18:02       58 阅读
  6. 【mysql】drop、deletetruncate的区别

    2024-04-23 20:18:02       45 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-23 20:18:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-23 20:18:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-23 20:18:02       82 阅读
  4. Python语言-面向对象

    2024-04-23 20:18:02       91 阅读

热门阅读

  1. Php 通过 FFmpeg 获取远程视频的时长和截图

    2024-04-23 20:18:02       34 阅读
  2. 数字人技术:相关论文汇总

    2024-04-23 20:18:02       33 阅读
  3. Redis雪崩

    2024-04-23 20:18:02       38 阅读
  4. python多线程详解

    2024-04-23 20:18:02       30 阅读
  5. Ubuntu搭建RP2040开发环境-1

    2024-04-23 20:18:02       37 阅读
  6. Springboot2.7解决静态资源302问题

    2024-04-23 20:18:02       39 阅读
  7. LeetCode 42. 接雨水 - PHP

    2024-04-23 20:18:02       32 阅读