浅谈C/C++内存管理

C/C++内存管理

这期我们来介绍一下C/C++中的内存管理

文章目录

C/C++内存分布

cpp 复制代码
int globalval = 1;
static int staticGlobalvar = 1;
void Test() {
	
	static int staticvar = 1;
	int localval = 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.代码段(常量区)

globalVar在哪里?____ staticGlobalVar在哪里?____

staticVar在哪里?____ localVar在哪里?____

num1 在哪里?____

char2在哪里?____ *char2在哪里?____

pChar3在哪里?____ *pChar3在哪里?____

ptr1在哪里?____ *ptr1在哪里?____

看到这里的同学肯定是一脸懵逼,哪里有一上来就写题目的,待看到下面的图后,大部分的填空题都迎刃而解

  1. 栈又叫堆栈------存储非静态局部变量/函数参数/返回值等等,栈是向下增长的
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可以使用系统接口创建共享内存,做进程间通信
  3. 堆是用于程序运行时动态内存分配,堆是可以向上增长的
  4. 数据段------存储全局数据和静态数据
  5. 代码段------存储可执行代码、只读常量、字面量等

在回顾前面的问题

选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

globalVar在哪里?

C

staticGlobalVar在哪里?

C

staticVar在哪里?

C

localVar在哪里?

A

num1 在哪里?

A

char2在哪里?

A

前面几道是轻而易举的,到了后面几道就有点懵逼了。可能有的同学会把*char2解引用成"abcd"因而就选择了D

这个是错误的,因为"abcd"是拷贝给char2的,而char2在栈区,因此*char2解引用是在栈区的,因此*char2在栈区

pchar3毋庸置疑是在栈区,*pchar3解引用后是"acbd",但是前面有一个const修饰*pchar3所指向的内容(注意是修饰指针所指向的内容,不是修饰指针,因为修饰指针需要这样子写:const char* pChar3 const = "abcd";,在指针后面写const才是修饰指针,因此*pchar3所指向的内容是数据段,也就是静态区

ptr1在栈区,*ptr1使用了malloc函数在堆上开取空间,因此在堆区

这里重新回顾一下C语言的知识

c 复制代码
sizeof(num1) = ____;  
sizeof(char2) = ____;
strlen(char2) = ____;
sizeof(pChar3) = ____;   
strlen(pChar3) = ____;
sizeof(ptr1) = ____;

答案是

sizeof(num1)=40 sizeof计算所有的大小:也就是int:4字节 * 数组大小:10=40字节

sizeof(char2)=5 char2是字符串,结尾包括\0,因此sizeof的结果是"abcd"+'\0'=5字节

strlen(char2)=4strlen遇到\0就停下来,因此只包括前面的字符串,也就是"abcd"=4字节

sizeof(pChar3)=4/8,因为pChar3是指针,在32位平台下是4字节,64位平台下是8字节

strlen(pChar3)=4strlen遇到\0就停下来,因此只包括前面的字符串,也就是"abcd"=4字节

sizeof(ptr1)=4/8因为ptr1是指针,在32位平台下是4字节,64位平台下是8字节

C语言内存管理方式

c 复制代码
int main() {
	
	int* p1 = (int*)malloc(sizeof(int) * 4);
	free(p1);
	int* p2 = (int*)calloc(4, sizeof(int));
	int* p3 = (int*)realloc(p2, sizeof(int));

	free(p3);
	//free(p2);

	return 0;
}

其中有malloccallocreallocfree函数

可以在c语言的参考手册上可以看见

为什么不需要free(p2)呢,因为realloc是空间不够时,扩容用到的,这里还没有用到对应的内存,因此不需要free

C++内存管理方式

可以看见C语言在内存管理方面,有点繁琐,并且在某些方面是无能为力的,因此C++推出了属于他的内存管理方式:newdelete。当然,C++是兼容C的,因此C语言的内存管理在C++中也能用

new/delete操作内置类型

cpp 复制代码
int main() {
	//动态申请一个int类型的空间
	int* ptr1 = new int;
	//动态申请一个int类型的空间,并且初始化为10
	int* ptr2 = new int(5);
	//动态申请5个int类型的空间
	int* ptr3 = new int[5];
	//动态申请5个int类型的空间,并且全部初始化为0
	int* ptr4 = new int[5] {0};
	//动态申请5个int类型的空间,并且部分初始化,其余全为0,与c语言的部分初始化类似
	int* ptr5 = new int[5] {1, 2, 3};
	//回收资源
	delete ptr1;
	delete ptr2;
	delete[] ptr3;
	delete[] ptr4;
	delete[] ptr5;


	return 0;
}

调试结果(未delete之前)

delete

可以看见delete后,编译器对内置类型,就把所对应的内存给释放掉(释放地址),并没有把已经初始化的值给清理掉(和mallocfree是没有区别的)

有了new和delete之后,动态申请内存就没有C语言这么麻烦了,不需要手动计算大小,操作便捷。正所谓学编程的人的对象,都是可以new的

new/delete操作自定义类型

cpp 复制代码
int main() {
	
	A* p1 = new A;
	A* p2 = new A(5,6);
	A* p3 = new A[5];
	A* p4 = new A[5]{ {1,2},{3,4},{5,6} };

	//malloc和new的区别就在于,new首先会创建空间,然后去调用类的默认构造函数初始化,而malloc不会调用默认构造
	A* m1 = (A*)malloc(sizeof(A));
	int* p = (int*)malloc(sizeof(int) * 5);
	

	//同时free和delete的区别于,delete会调用析构函数,而free不会
	//malloc/free在内置类型上,和new/delete没有区别
	delete p1;
	delete p2;
	delete[] p3;
	delete[] p4;
	free(p);
	free(m1);

	return 0;
}

调试(未delete前)

delete后

运行结果

对于自定义类型,new会去调用类的默认构造,上面代码中,new12个对象,就调用了12次默认构造,同样delete也是调用了12次析构函数

因此,C++推出newdelete是为了更好的支持自定义类型,符合面向对象编程的逻辑

operator new和operator delete函数

在cpluscplus的参考手册上是这样写的

operator new can be called explicitly as a regular function, but in C++, new is an operator with a very specific behavior: An expression with the new operator, first calls function operator new (i.e., this function) with the size of its type specifier as first argument, and if this is successful, it then automatically initializes or constructs the object (if needed). Finally, the expression evaluates as a pointer to the appropriate type.[operator new - C++ Reference](https://legacy.cplusplus.com/reference/new/operator new/)

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

cpp 复制代码
/*
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 delete: 该函数最终是通过free来释放空间的
*/
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;
}

/*
free的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

operator new 和operator delete底层代码

new和delete的实现原理

内置类型

newdelete在内置类型上,基本和mallocfree的功能一致,不同的地方:new/delete申请和释放的是单个的内存空间,new[]/delete[]申请和释放的是连续的内存空间,new失败时返回异常,malloc失败返回NULL

自定义类型

new的原理

  1. 先调用operator new开辟空间
  2. 在开辟的空间执行构造函数

delete的原理

  1. 先调用operator delete
  2. 在调用自定义类的析构函数

new[]的原理

  1. 先调用operator new[]开辟N个空间
  2. 在开辟的空间执行N次构造函数

delete[]的原理

  1. 先调用operator delete[]
  2. 在调用N次自定义类的析构函数

定位new表达式

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

格式

cpp 复制代码
ptr = (type*)operator new(ptr) type;
ptr = (type*)operator new(ptr) type(initializer_list);

其中type*是类型指针,ptr是指针,initializer_list是初始化列表,type是类型

应用场景

定位new主要搭配内存池技术使用

万字详解C++内存池:提高内存分配效率的利器

示例:

cpp 复制代码
class A {
public:
	A(int x = 0, int y = 0)
		:_a(x),
		_b(y)
	{
		//查看是否运行构造函数
		cout << "A():" << endl;
	}

	~A() {
		//查看是否运行析构函数
		cout << "~A()" << endl;
	}

private:
	int _a;
	int _b;
};

int main() {

	A* p1 = (A*)operator new(sizeof(A));
	new(p1)A(10);
	p1 ->~A();
	operator delete(p1);

	return 0;
}

在C++中,能不能通过指针的方式调用构造函数和析构函数呢,显然,构造函数是没办法实例化的,因为是实例化对象时自动调用的,是编译器处理的,但是析构函数可以通过定位new的方式来调用

new/delete和malloc/free的区别(面试题

相同点

都是从堆上申请空间,都需要用户手动释放

不同点

  1. malloc/free是函数,new/delete是操作符
  2. malloc申请的空间不会初始化,new会初始化(有构造函数)
  3. malloc在申请空间时,还需要sizeof来手动计算类型大小,new只需要在后面跟上类型即可,如果需要new多个,在new []中指定数量即可
  4. malloc申请空间失败时,返回的是NULL(在学数据结构时,肯定会有一个判断if(ptr==NULL),就输出或返回fail),而new不需要判空,只需要捕获异常:try catch语句
  5. malloc的返回值是void*,在使用时必须强转((int*)malloc(sizeof(int))),而new不需要,因为new后面紧跟着的是对应的数据类型
  6. 在申请自定义类型的空间时,malloc/free只申请空间/释放空间,不能调用构造函数和析构函数,而new/delete可以

内存泄漏

**定义:**什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

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

分类

C/C++程序中,我们最主要关心两种方面的内存泄漏

堆内存泄漏(Heap Leak)

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

系统资源泄漏

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

如何检测

在VS下,可以使用Windous下提供的_CrtCheckMemory()函数查看大概泄漏了多少内存,仅仅简单检查,并不能准确查找泄漏位置

cpp 复制代码
int main() {

	//1.忘记释放空间
    int* p1 = new int[10];
    int* p2 = new int[100];

    _CrtDumpMemoryLeaks();

	return 0;	

}

调试结果

因此,在写代码过程中,一定要注意内存释放,这也是cpp最容易出问题的地方。创建空间一定要记得释放。

如果工程过大,泄漏位置多,可以使用以下工具

Linux:Linux下几款C++程序中的内存泄露检查工具_c++内存泄露工具分析-CSDN博客

Windows:【VS2019】C/C++内存泄漏检测工具:Visual Leak Detector超详细安装教程(for windows)_windows leaks detector安装使用-CSDN博客

Other:内存泄露检测工具比较 - 默默淡然 - 博客园

如何避免

1.工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。

ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。

2.采用RAII思想或者智能指针来管理资源。

3.有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。

4.出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:

内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

相关推荐
lin zaixi()1 小时前
C++ STL 快速实现全排列
数据结构·c++·算法
遮天华月3 小时前
C++ 运算符重载详解
c++·学习·算法
宏阳李老师3 小时前
2024-12 GESP C++ 二级试题及答案解析
开发语言·数据结构·c++·算法·青少年编程·gesp·csp
Dola_Pan3 小时前
C语言:const的用法
c语言·开发语言·数据结构
字节高级特工6 小时前
C++---入门
开发语言·c++·算法
亦是远方7 小时前
C++编译过程
java·开发语言·c++
萝诗粉7 小时前
c语言在程序开发过程的各个阶段遇到的问题和知识总结
c语言·开发语言
刘好念7 小时前
[OpenGL] Transform feedback 介绍以及使用示例
c++·计算机图形学·opengl
快乐飒男8 小时前
C语言基础13(内存管理)
c语言·开发语言·学习