IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C/C++内存管理 -> 正文阅读

[C++知识库]C/C++内存管理

前言

作者小蜗牛向前冲

名言我可以接受失败,但我不能接受放弃

??如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎?大家在评论区指正。

目录

一、C/C++中的内存分布

1、内存分布的基础知识

?2、内存分布的一道面试题

二、C++的内存管理方式

1、C++的动态内存开辟和释放

2、new/detele和malloc/free的区别

三、深入理解new和delete

1、new/delete底层机制的实现

2、定位new表达式

?3、内存泄漏


在本期学习目标:了解C/C++中的内存分布?,对比C语言和C++开辟的内存方式(new/delete),深入理解new和delete

一、C/C++中的内存分布

1、内存分布的基础知识

在计算机存储中存在怎么几个区域需要我们去了解

  • :栈又叫堆栈,栈区中主要存放函数的返回值/函数的参数/非静态的成员变量,栈的特点是向下增长的(从栈申请的内存地址会越来越小)
  • 内存映射段:是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口 创建共享共享内存,做进程间通信。
  • 堆区:主要要用于程序的动态开辟,堆的特点是向上增长的。
  • 数据段:存放全局变量和局部变量。
  • 代码段:存储可执行文件的指令/只读常量。

?2、内存分布的一道面试题

下面看一段代码,在细细体会一下一个代码中的变量都存储在计算机的那些位置:

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
1. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?___C_ staticGlobalVar在哪里?_C__
staticVar在哪里?_C__ localVar在哪里?___A_
num1 在哪里?____A
char2在哪里?__A__ *char2在哪里?__A_
pChar3在哪里?___A_ *pChar3在哪里?___D_
ptr1在哪里?_A___ *ptr1在哪里?____B
2. 填空题:
sizeof(num1) = ___40_;
sizeof(char2) = __5__; strlen(char2) = ___4_;
sizeof(pChar3) = ____4/8; strlen(pChar3) = __4__;
sizeof(ptr1) = ____4/8;

我们可以对比上面的图来理解这道题:

1 选择题

  • globalVar他是一个全局变量,所以他存在在数据段
  • staticGlobalVar和staticVar,一个是有static修饰的全局变量,另外一个是静态变量,所以都存放在数据段
  • localVar和num1都是属于局部变量,所以存放在栈中。
  • char2和*char2,虽然”abcd"是一个常变量应该存放在常量区,但是char2是数组,而*char2是指数组的首元素a,但是数组是存在栈区的,所以a也应该在栈区。
  • pChar3和*pChar3,pChar3是一个指针也是存在栈区的,但是*pChar3找到了a而“abcd”是一个字符常量是存在常量区的。
  • ptr1也是一个指针所以存在栈区,而*ptr1指向的是malloc动态开辟的空间,存在堆区中。

2 填空题

  • sizeof(nim1)是求整个数组的大小,所以是40个字节
  • sizeof(char2)也是求整个数组的大小,要包括'\0',所以是5个字节,strlen(char2)是求字符串的个数所以是4个。
  • sizeof(pChar3)求的是指针的大小指针在32位的机器下大小位4个字节而在64位的机器下位8个字节,而strlen求的是字符串的个数4个。
  • sizeof(ptr1)也是求指针的大小。

二、C++的内存管理方式

1、C++的动态内存开辟和释放

以前在C语言中我们就学习跟动态内存开辟的几个函数:malloc函数和calloc及realloc函数这些函数的区别就不在多说了(感兴趣的小伙伴可以查看我之前写的博客)。

虽然C++在语法上是兼容C语言的,但是因为某些原因(下面会说),C++又用关键字来new来开辟动态空间,用delete关键字来释放动态开辟的空间

这里通过调试可以看到new位p开辟了一个整形的空间,但是p中存在的是一个随机值,那么又该如何去初始化呢?

?这里我们只要在括号中传我们想要初始化的值就可以了。

?注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。如果不匹配可能会出现各种错误。

2、new/detele和malloc/free的区别

操作内置类型

int main()
{
	int* p1 = new int;
	int* p2 = (int*)malloc(sizeof(int));
	if (p2 == nullptr)
	{
		perror("fail malloc");
		exit(-1);
	}
	p1 = nullptr;
	free(p2);
	delete p2;
	p2 = nullptr;
	return 0;
}

上面代码p1和p2开辟的动态空间有什么区别吗?

?我们观察到对于内置类型来说new和malloc开辟的动态空间基本类似,不同的地方是: new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

操作自定义类型

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	A* p1 = (A*)malloc(sizeof(A));
	if (p1 == nullptr)
	{
		perror("fail malloc");
		exit(-1);
	}
	A* p2 = new A(1);
	free(p1);
	delete p2;
}

?将代码运行起来会出现什么情况呢?

?这里我们发现用new/delete不仅为了类A开辟了动态空间,new调用了类构造函数delete调用了析构函数,而malloc函数是没有这给功能的。

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来释 放空间

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

new/detele和malloc/free的区别(从用法,和底层实现)

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

三、深入理解new和delete

1、new/delete底层机制的实现

我们知道new和delete是用来动态开辟和释放空间的操作符(关键字,那么他们又是如何去实现呢?

这就不得不说operator new 和operator delete是 系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间

operator new函数

这里大家要主要,new不是重载,而是一个全局函数?

这里来看一段代码的反汇编:

?

?在我们用new开辟动态空间,最重要的是调用了二个函数,这里我们重点关注operator new函数,那这个函数的底层又是什么呢?

?这里可以看到底层是用的malloc开辟空间的,那有小伙伴可能就要问了,为什么不直接用malloc开辟而要弄出一个operator new函数来进行封装一下呢?因为C++语言是面对对象的语言,而且new开辟空间后是不需要检测开辟是否成功的,但是需要抛异常(以后会为大家分享),所以要用operator new函数进行封装一下这样才符合C++面对对象出来错误的方式。

operator delete函数

这个函数的出现也是为了封装delete,下面我们直接看他的底层是如何实现的:

?从上面我们可以看出opertaror底层的实现也是调用了free的。

总结:

通过上述两个全局函数的实现知道:

  • operator new 实际也是通过malloc来申请空间,如果 malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施 就继续申请,否则就抛异常。
  • operator delete 最终是通过free来释放空间的。

2、定位new表达式

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

?使用格式: new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针initializer-list是类型的初始化列表

使用场景: 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

来看下面这段代码来理解一下:

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
// 定位new/replacement new
int main()
{
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没执行
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参
	p1->~A();
	free(p1);
	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);
	return 0;
}

先来看到指针p1,我们用malloc来开辟的一个动态空间来存储来存放类的空间,但是他现在还能说是建立了一个对象,因为这里析构函数是没有执行的,这时候我们就可以用new的定位表达式主动调用析构函数(我们知道析构函数以前在类外是不能主动调用的)。

对于p2指针来说我们是用operator new(new关键字的底层实现,其实也就是malloc)来开辟动态空间的,这里在new(p2)A(10),不仅仅调用来析构函数,还将类中成员变量a初始化为10。

?3、内存泄漏

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况内 存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制(也就是说我们没有指针指向这里了),因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死

内存泄漏分类:

  • 堆内存泄漏(Heap leak) :堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一 块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分 内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  • 系统资源泄漏 :指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

你们内存泄漏既然如此后果既然如果严重,那么我们有什么方法检测出来吗?

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

?这时候有处不大,还是要我们去找哪里出现了内存泄漏(毕竟谁会承认自己写出了dug),而且如果工程比较大,内存泄漏位置比较多,不太好查时 一般都是借助第三方内存泄漏检测工具处理的(这里检测的工具就不在说了,种类有些多,在不同的操作系统检测工具都不同)。

总结:

  • 对于内存泄漏我们还是要保持警惕,自己开辟了动态空间一个要记得用free或者delete去释放。
  • 对于内存泄漏我们一般有二种解决方法:
  1. 事前预防,如用智能指针等。
  2. 事后检测,用等三方的检测内存泄漏的工具。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-22 20:56:54  更:2022-10-22 20:57:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 3:42:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码