[C++内存管理]

本文详细解释了C/C++中的内存分布,包括栈、堆、数据段(静态区)和代码段(常量区)的区别,以及new/delete与malloc/free等内存管理函数的用法。还讨论了构造函数和析构函数在内存分配中的作用,并提到了内存泄漏的概念及其危害。
摘要由CSDN通过智能技术生成

1. C/C++内存分布

首先来看看内存的分布情况:

接下来在看看下面的一段代码:

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.代码段(常量区)
   globalVar在哪里?__1__   staticGlobalVar在哪里?__2__
   staticVar在哪里?__3__   localVar在哪里?__4__
   num1 在哪里?__5__
   
   char2在哪里?__6__   *char2在哪里?_7__
   pChar3在哪里?__8__      *pChar3在哪里?__9__
   ptr1在哪里?_10___        *ptr1在哪里?__11__

我们来依次回答一下上面的问题

1. globalVar是全局变量,但不是静态变量,在结合上面图片的内容,我们可以知道他在数据段(静态区)

2.staticGlobalVar是全局的静态变量,故在数据段(静态区)

3.  staticVar是静态变量,生命周期贯穿整个程序,故在数据段(静态区)

4. localVar是main函数内的局部变量,结合上图可得在

5. num1和4同理也是局部变量,同时也是数组,故存储在

6.  char2 是局部变量,它是数组首元素的地址,存储在

7.*char2是指向char2字符串内容的首元素,同时char2在栈上,故也存在

8. pChar3是用const 修饰的字符指针,印其是局部变量,结合上面的图所式,故存储在

9.*pChar3是指向"abcd"字符串的首元素的指针,而"abcd"的字符串是不能修改的,即只读,其存储在代码段(常量区)里面,*pChar3指向其内容的,故也在代码段(常量区)

10.  ptr1是int类型的指针,其也是局部变量,故存储在

11. *ptr1是解引用指向malloc所开辟的空间,而malloc所开辟的空间在堆里面,故ptr1在

总结:

*char2不在常量区,因为char2是局部字符数组,其内容直接存储在栈上。
*pChar3在常量区,因为它指向的是一个字符串字面量,字符串字面量被存储在程序的常量区域,这部分内存是只读的。
当我们讨论变量存储在哪里时,通常涉及到几个关键区域:栈(Stack)、堆(Heap)、数据段(Data Segment,又称静态区)、和代码段(Code Segment,又称常量区)。每种类型的变量根据其特性和声明周期被存储在这些区域中的相应位置

是用于存储局部变量、函数参数等的内存区域。当一个函数被调用时,其局部变量和一些书keeping信息被推入栈中;当函数执行完成,这些信息被从栈上弹出。栈是自动管理的,开发者无需手动分配或释放内存。

是用于动态内存分配的内存区域。不同于栈,开发者需要显式地从堆上分配内存(如使用malloc或new),并在不再需要时释放这些内存(如使用free或delete)。

数据段,又称为静态区,用于存储全局变量、静态变量等。这些变量的生命周期贯穿整个程序执行期,因此它们被存储在一个特定的、持久的内存区域中。

代码段,又称为常量区,用于存储程序的执行代码和常量数据,如字符串字面量。这部分内存是只读的,用来保证程序代码的安全性。

C语言中动态内存管理方式:malloc/calloc/realloc/free

在C语言中,动态内存管理是通过一组标准库函数完成的,包括malloc, calloc, realloc, 和 free。这些函数允许程序在运行时动态地分配、调整和释放堆内存,这是对于管理变化的数据量和大小特别有用的能力。下面是这些函数的基本用法和它们之间的区别:

malloc

用法:void* malloc(size_t size);
功能:分配指定字节数的未初始化内存。它返回一个指向分配的内存的指针。如果分配失败,返回NULL。
示例:int* ptr1 = (int*)malloc(sizeof(int) * 10); 这行代码为10个整数分配了内存

calloc

用法:void* calloc(size_t num, size_t size);
功能:为指定数量的元素分配内存,每个元素的大小也在参数中指定,并自动初始化所有位为0。如果分配失败,返回NULL。
示例:int* ptr2 = (int*)calloc(10, sizeof(int)); 这行代码为10个整数分配了内存,并将它们初始化为0。


realloc

用法:void* realloc(void* ptr, size_t size);
功能:调整之前调用malloc或calloc分配的内存块的大小。如果新的大小大于原始大小,可能会移动内存块到新的位置以提供足够的连续空间。如果realloc的第一个参数是NULL,它的行为就像malloc。
示例:int* ptr3 = (int*)realloc(ptr1, sizeof(int) * 20); 这行代码将之前分配的内存大小调整为20个整数的大小。


free

用法:void free(void* ptr);
功能:释放之前通过malloc, calloc, 或 realloc分配的内存。一旦内存被释放,那块内存就不能再被访问了。

注意:尝试释放未经分配的内存块或多次释放同一个内存块是不安全的,可能导致未定义行为

注意事项:

在使用这些函数时,确保正确处理内存分配失败的情况,并在内存不再需要时使用free来避免内存泄露。
当使用realloc时,如果分配失败,原始内存不会被释放。故,建议先将realloc的返回值赋给一个临时指针,以检查是否分配成功,再重新赋值给原始指针,避免内存泄漏。
始终确保只对通过malloc, calloc, 或 realloc分配的指针使用free,并且每个分配的内存块只被free一次

3. C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,比如:无法对自定义类型进行内存管理,因此C++又提出了自己的内存管理方式:通过newdelete操作符进行动态内存管理.

3.1new和delete的使用方式:

int main()
{
	int* ptr1 = new int;
	int* ptr2 = new int(10);
	int* ptr3 = new int[10];
	int* ptr4 = new int[10] {1, 2, 3, 4};
	
	delete ptr1;
	delete ptr2;
	delete []ptr3;
	delete []ptr4;

	return 0;
}
注意:申请和释放单个元素的空间,使用 new delete 操作符,申请和释放连续的空间,使用 new[] delete[] ,注意:匹配起来使用。

3.2 newdelete操作自定义类型

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 = new A(1);
	delete p1;
	return 0;
}

new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间,还会调用构造函数和析构函数

调用了构造函数

调用了析构函数

我们发现,汇编代码中有这一步:

 call operator new 

4. operator newoperator delete函数

new delete 是用户进行 动态内存申请和释放的操作符 operator new operator delete 是系统提供的 全局函数 new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局 函数来释放空间
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}

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

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

operator delete: 该函数最终是通过free来释放空间的

 

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

5. newdelete的实现原理

5.1 内置类型

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

5.2 自定义类型

  • new的原理
  • 调用operator new函数申请空间
  • 在申请的空间上执行构造函数,完成对象的构造

  • delete的原理
  • 在空间上执行析构函数,完成对象中资源的清理工作
  • 调用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来释放空间

 6. 定位new表达式(placement-new)

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

使用格式:
new (place_address) type或者new (place_address) type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

如下图所示:
class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
	// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
	A* p1 = (A*)malloc(sizeof(A));
	new(p1)A;
	p1->~A();
	free(p1);
	return 0;
}

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

7.C/C++内存相关概念

7.1 malloc/freenew/delete的区别

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在释放空间前会调用析构函数完成空间中资源的清理

 

 7.2 内存泄漏

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死
 

分类:

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

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

本节内容到此结束!!感谢观看

  • 13
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WEP_Gg

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值