c/c++的内存管理(超详细)

news2024/11/13 8:45:47

一、c/c++的内存分布

这是操作系统中对于内存的划分:

我们重点掌握以下几个区域即可:

1.栈 (调用函数会建立栈帧)   2.堆(动态开辟的空间)  3.数据段(静态区):存放静态变量以及全局变量 4.代码段 (常量区)


先来看看一个题目:

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);
}

选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
1. globalVar在哪里?____ 2. staticGlobalVar在哪里?____  3. staticVar在哪里?____
4. localVar在哪里?____ 5. num1 在哪里?____  6. char2在哪里?____
7. *char2在哪里?___  8. pChar3在哪里?____  9. *pChar3在哪里?____
10. ptr1在哪里?____ 11. *ptr1在哪里?_______

分析开始:1. globalVar定义在全局中为全局变量选

2. staticGlobalVa定义在全局中,属于全局静态变量选C   

3.staticVar定义在main函数中的局部静态变量选

4. localVar定义在main函数中的局部变量选 A 

5. num1定义在main函数中的局部变量选A  是一个整型指针

6.char2定义在main函数中的局部变量选A  是一个字符指针

7. *char2定义在main函数中的局部变量选A 是一个字符

8. pChar3定义在main函数中的局部变量选A 是一个常量指针 

注意常量指针是指向的内容不变,而指针常量是指向的对象不变

9. *pChar3定义在main函数中的常量选D 是一个字符常量

10.ptr1定义在main函数中的局部变量选A 是一个用来接收动态开辟空间的指针

11. *ptr1是动态开辟的空间在堆区选B

结果是 C C C A A A A A D A B

还要补充几点知识:

1一般而言显示初始式的全局变量一般是自动是0;

2而局部变量没有显式初始化一般都是随机值;

3静态变量必须显式初始化,否则报错

二、c语言内存管理

2.1动态内存函数

void* malloc (size_t size);

1. 如果开辟成功,则返回一个指向开辟好空间的指针。
2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
3. 返回值的类型是void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
4.如果参数size 为0,malloc的行为是标准是未定义的,取决于编译器

void free (void* ptr);

1. 如果参数ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的
2. 如果参数ptr 是NULL指针,则函数什么事都不做。

 malloc和free都声明在stdlib.h 头文件中。

 这里浅浅举个例子

#include<stdlib.h>
int main()
{
	int n;
	scanf("%d", &n);
	int* p = (int*)malloc(sizeof(int) * n);
	if (NULL != p)
	{
		memset(p, 0,4*n);
	}
	free(p); p = NULL;
	return;
}

这就是一个简单的开辟空间,并且初始化的过程了

void* calloc (size_t num, size_t size);

函数的功能是为num 个大小为size 的元素开辟一块空间,并且把空间的每个字节初始化0
与函数malloc 的区别只在于calloc 会在返回地址之前把申请的空间的每个字节初始化为全0

 

 这样就可以清楚的看到每一个值都被初始化为0

void* realloc (void* ptr, size_t size);

  ptr 是要调整的内存地址,size 调整之后新大小,返回值为调整之后的内存起始位置。

realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那realloc 函数就可以做到对动态开辟内存大小的调整。


这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化

情况2:原有空间之后没有足够大的空间,扩展的方法是:在堆空间上另找一个合适大小
的连续空间来使用。这样函数返回的是一个新的内存地址。当然它会把原来的数据拷贝到新的空间并且会自动删除原有空间

情况3:我们直接记住使用realloc时,有可能开辟失败,如果直接让原来的指针接收会有内存泄漏,所以使用realloc时要多加一个变量

int *p=(int *)malloc(20);

int *ptr=(int *)realloc(p,40);

if(NULL==ptr){perror("realloc fail");}

else

{p=ptr;}

2.2 常见的内存错误

 1 对NULL指针的解引用操作 

int *a=NULL;
*a=20;//error
void test()
{
int *p = (int *)malloc(INT_MAX/4);//这里开辟的空间挺大的
*p = 20;//如果p的值是NULL,就会有问题
free(p);}

对动态开辟空间的越界访问

int *a=(int *)malloc(sizeof(int)*100);
a[100]=10;//越界了

3 对非动态开辟内存使用free释放

int a=10;int *p=&a;free(p);

4 使用free释放一块动态开辟内存的一部分

int *a=(int *)malloc(sizoef(int)*20);free(a+10);

 5 对同一块动态内存多次释放

int *p=(int *)malloc(sizeof(int)*10);free(p);free(p);

6. 动态开辟内存忘记释放(内存泄漏)

int Test()
{
int *p=(int *)malloc(sizeof(int)*10);return *p;
}
int main()
{
printf("%d",Text());
}

2.3 经典笔试题

判断下面程序的结果

第一个

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}
int main()
{
	Test();
	return 0;
}

分析:str首先是一个空指针,通过传值传参产生的临时变量被分配了空间,但是str并没有改变

所以这里存在内存泄漏。并且最终还打印null指针这个会导致程序崩溃。

第二题

char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
int main()
{Test();return 0;}

分析:这里是出现了野指针问题,str接收到了一个没有被编译器使用的的空间,此时这种行为是未定义的,在vs里出现了乱码,但是程序没有崩溃。

代码为0就是程序正常结束

 第三题

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}
int main()
{
	Test();
	return 0;

分析:这个代码看起来没有问题,实际上,他只是没有free掉空间,仅此而已,程序没有崩溃,但是存在内存泄漏。

第四题 

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
int main()
{
	Test();
	return 0;
}

分析:不过这个就要分情况了

1.如果str开辟失败,str为空,调用strcpy会导致程序崩溃,但是此时没有内存泄漏(一般不会失败)

2.如果开辟成功,str不为空,但是后面会free掉,此时str为野指针,此时再次strcpy会把不属于程序的空间变成world,(当然这个是未定义的行为,取决于编译器)结果就打印了world了

虽然看起来能跑,但是这是错误程序

三、c++内存管理

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过newdelete操作符进行动态内存管理

 3.1 new/delete操作内置类型

直接看使用

int main()
{
	//开辟一个空间,初始化为10
	int* p1 = new int(10);
	//开辟一个空间不初始化
	int* p2 = new int;
	//开辟十个空间半初始化
	int* p3 = new int[10] {1, 2, 3, 4, 5, 6};
	//开辟空间十个空间不初始化
	int* p4 = new int[10];
	//清理空间,一个空间不加[] ,多个空间加上[],一定要配套使用
	delete p1;
	delete p2;
	delete[]p3;
	delete[]p4;
	return 0;
}

直接说结论:对于开辟一次空间,初始化为初始化值,不初始化为随机值

对于开辟数组,初始化为初始化的值,初始化一半剩下值自动初始化为0,不初始化,全部为随机值

注意要配套使用哦!!!当然对于内置类型而言配不配套都不影响,但是要养成好的代码习惯

这里还是举个例子至于为什么,后续再说

int main()
{
	char* ch0 = new char;
	char* ch1 = new char[10];
	char* ch3 = (char*)malloc(1);
	char* ch4 = (char*)malloc(10);
	free(ch0); free(ch1); delete ch3; delete[]ch4;
}

我们这么乱配套除了有警告之外,程序是没有问题的 

3.2 new和delete操作自定义类型

我们先构造一个类以便后续操作:

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
		int _a;
};
//这里是一个有析构和构造的类

1. 调用new以及delete时会使用构造和析构

看结果,可以知道new和delete多了一个构造和析构,这也是与malloc和free的最大区别

对于自定义类型构造和析构是很重要的

2. new[] 和delet [] 与new delete的区别

加上一个[]只不过是开辟更多的空间以及调用多次构造与析构而已!

3.3 new 和delete的原理

1 operator new与operator delete函数

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

 下面是operator new的实现逻辑

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

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
		return (p);
}

总结:operator new的底层是malloc

下面是operator delete的实现逻辑

void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
#define free(p) _free_dbg(p, _NORMAL_BLOCK

实际上operator delete的底层也是free();

2.new和delete实现的功能

内置类型

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

 自定义类型

new的原理
1. 调用operator new函数申请空间


2. 在申请的空间上执行构造函数,完成对象的构造

3. 同理对于new[N] 来说,它会调用 operator new[]在operator new[]中实际调用operator new 函数完成N个对象空间的申请,以及构造N个构造函数

delete的原理
1. 在空间上执行析构函数,完成对象中资源的清理工作


2. 调用operator delete函数释放对象的空间

3.同理对于delete[]来说:释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

 3.4 抛异常

使用catch try捕获异常

int main()
{
	while (1)
	{
		static int n = 1;
		//1MB
		try {
			int* a = new int[1024 * 1024];
			cout << a << "  " << n << endl;
		}
		catch (const exception& e)
		{
			cout << e.what() << endl; break;
		}
		++n;
	}
	return 0;
}

运行一下大概是7161*4MB的内存,不过反过来也可以使用这种方法来估算内存

我们可以算一算大概给了多少内存:27.984375

这个是虚拟内存实际比这个要小

重点还是抛异常是c++应对内存开辟失败的处理方法

3.5 定位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 = 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;
}

四、总结

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


2. malloc申请的空间不会初始化,new可以初始化


3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,
如果是多个对象,[]中指定对象个数即可


4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型


5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需
要捕获异常


6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new
在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成
空间中资源的清理释放

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1952827.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JDK的配置

安装好JDK后&#xff0c;配置三个环境变量 第一步&#xff0c;配置JAVA_HOME. 先找到JDK的安装目录&#xff0c;然后复制路径&#xff0c;在电脑的环境变量里增添变量名为JAVA_HOME,变量值为 C:\Program Files\Java\jdk1.8.0_192。&#xff08;具体根据你的JDK安装路径&…

Lombok的认识

Lombok的作用 Lombok是一个Java库&#xff0c;它可以通过简单的注解形式来帮助开发人员简化Java代码的编写&#xff0c;特别是减少模板代码的书写。具体来说&#xff0c;Lombok的主要作用包括&#xff1a; 减少模板代码&#xff1a;Lombok可以通过注解自动生成getter、setter、…

Python——Pandas(第三讲)

文章目录 修改替换变量值对应数值的替换指定数值范围的替换 虚拟变量变换数值变量分段数据分组基于拆分进行筛选 分组汇总使用 agg 函数进行汇总引用自定义函数 长宽格式转换转换为最简格式长宽型格式的自由互转 多个数据源的合并数据的横向合并concat 命令 处理缺失值认识缺失…

【Apache Doris】3.0存算分离|标准部署篇(一)

【Apache Doris】3.0存算分离&#xff5c;标准部署篇&#xff08;一&#xff09; 一、前提概要二、环境信息三、前置准备四、FoundationDB安装五、OpenJDK 17安装六、 Meta Service安装七、集群安装八、快速体验 接上 数据架构新篇章&#xff1a;存算一体与存算分离的协同演进。…

Meta 发布Llama 3.1开源模型 NVIDIA推出AI 代工服务

在这周二&#xff0c;Meta发布了最新的AI模型Llama 3.1&#xff0c;并且是一个开源模型&#xff0c;面向公众免费提供&#xff0c;且提供8B、70B、305B参数版本&#xff0c;模型整体效果可与 GPT-4、GPT-4o、Claude 3.5 Sonnet 等领先的闭源模型相媲美。 此次Llama 3.1 系列改…

Hadoop3.3.5的安装与单机/伪分布式配置

文章目录 一、安装须知二、安装jdk三、安装shh四、安装配置hadoop五、运行hadoop 一、安装须知 本次安装的Hadoop版本为hadoop3.3.5。 在这之前完成了VMware虚拟软件的安装&#xff0c;并安装了Ubuntu22.04&#xff0c;在这基础上进行相关配置。 二、安装jdk 在Ubuntu中使用…

顺序表算法题

在学习了顺序表专题后&#xff0c;了解的顺序表的结构以及相关概念后就可以来试着完成一些顺序表的算法题了&#xff0c;在本篇中将对三道顺序表相关的算法题进行讲解&#xff0c;希望能对你有所帮助&#xff0c;一起加油吧&#xff01;&#xff01;&#xff01; 1.移除元素 2…

Lago - 使用 ClickHouse 扩展事件引擎

本文字数&#xff1a;4540&#xff1b;估计阅读时间&#xff1a;12 分钟 作者&#xff1a;Mathew Pregasen 本文在公众号【ClickHouseInc】首发 本周&#xff0c;我们欢迎来自 Lago 的一篇博客文章&#xff0c;介绍了他们如何使用 ClickHouse 扩展一个事件引擎&#xff0c;并在…

【JavaEE精炼宝库】 网络编程套接字——初识网络编程 | UDP数据报套接字编程

文章目录 一、网络编程基础1.1 网络编程的意义&#xff1a;1.2 网络编程的概念&#xff1a;1.3 网络编程的术语解释&#xff1a;1.4 常见的客户端服务端模型&#xff1a; 二、Socket 套接字2.1 Socket 套接字的概念&#xff1a;2.2 Socket 套接字的分类&#xff1a; 三、UDP数据…

24暑假算法刷题 | Day21 | LeetCode 669. 修剪二叉搜索树,108. 将有序数组转换为二叉搜索树,538. 把二叉搜索树转换为累加树

目录 669. 修剪二叉搜索树题目描述题解 108. 将有序数组转换为二叉搜索树题目描述题解 538. 把二叉搜索树转换为累加树题目描述题解 669. 修剪二叉搜索树 点此跳转题目链接 题目描述 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪…

Mac应用快速启动器:Alfred 5 for Mac 激活版

Alfred 5 是一款专为 macOS 系统设计的效率提升工具。这款软件以其快速启动和高效操作功能著称&#xff0c;通过使用快捷键来呼出输入界面&#xff0c;用户可以快速完成各种任务。 最新版本 Alfred 5.5 引入了一些新功能。其中包括整合了 ChatGPT 和 DALL-E&#xff0c;这意味…

Map的常见API

Map的常见API Map是双列集合的顶层接口&#xff0c;它的功能是全部双列集合都可以继承使用的 Map的遍历方式 map的遍历方式(键找值) package demo3;import java.util.HashMap; import java.util.Map; import java.util.Set;public class a1 {public static void main(String…

如何优化网站以提升UX设计质量

什么叫 UX 设计&#xff1f;UX 设计&#xff0c;即用户体验设计&#xff0c;是指为提升用户体验而进行的产品设计。 UX 在设计中&#xff0c;设计师通过调查和研究用户来掌握用户的需求和喜好&#xff0c;并利用这些信息来设计产品。设计师还会测试产品&#xff0c;以确保它们能…

xftp 如何链接 ubutn

一 如果你的ubutun 没有安装 openssh-server&#xff0c;那么是链接不上的&#xff0c;因此要先在ubutun 上一定要先安装openssh-server sudo apt install openssh-server 二 查看 ubutun 的ip 三 配置 xftp

导入vue3-引入富文本框编辑器-@wangeditor/editor库

效果图 下载依赖包 pureadmin/utils": "2.4.7 wangeditor/editor": "^5.1.23 wangeditor/editor-for-vue": "5.1.12定义公共组件 在src目录下定义 components/ReEditor/index.vue index.ts import editor from "./src/Editor.vue"…

BFS实现迷宫最短路径

结合队列的知识利用 广度优先遍历&#xff0c;通过对能走的路径的记录以及对走过路径的标记&#xff0c;进行多条路搜查 一、理论基础 如下图的迷宫&#xff1a; 选取所走方向&#xff08;针对某一个位置&#xff09;下&#xff0c;右&#xff0c;上&#xff0c;左&#xff0…

客户在哪儿AI——做真正管用的大客户获客方案

我们的目标是要打造一个真正“管用”的ToB大客户获客方案。以下是两个100%真实的案例&#xff0c;所有数据均为真实经营数据。一个是证明客户在哪儿AI对市场工作的颠覆性提升&#xff0c;另一个是证明客户在哪儿AI对决策层和销售工作的颠覆性提升。 客户在哪儿AI生产的是企业全…

C++模板——泛型编程

目录 1. 什么是泛型编程 2. 函数模板 2.1 定义格式 2.2 实例化及原理 2.3 参数匹配原则 3. 类模板 3.1 定义格式 3.2 实例化 4. 非类型模板参数 5. 模板的特化 5.1 概念 5.2 函数模板和类模板特化 6. 模板的分离编译 1. 什么是泛型编程 如何实现一个通用的加…

Windows下Pytorch入门深度学习环境安装与配置(CPU版本)

Windows下Pytorch入门深度学习环境安装与配置&#xff08;CPU版本&#xff09; 一、安装过程中各个软件的作用&#xff08;一&#xff09;Python&#xff08;二&#xff09;库 / 包 / package / library&#xff08;三&#xff09;PyTorch / Tensorflow&#xff08;四&#xff…

详解yolov5和yolov8以及目标检测相关面试

一、与yoloV4相比&#xff0c;yoloV5的改进 输入端&#xff1a;在模型训练阶段&#xff0c;使用了Mosaic数据增强、自适应锚框计算、自适应图片缩放基准网络&#xff1a;使用了FOCUS结构和CSP结构Neck网络&#xff1a;在Backbone和最后的Head输出层之间插入FPN_PAN结构Head输出…