优化C++资源利用:探索高效内存管理技巧

W...Y的主页 😊

代码仓库分享💕 


🍔前言:
我们之前在C语言中学习过动态内存开辟,使用malloc、calloc与realloc进行开辟,使用free进行堆上内存的释放。进入C++后对于动态内存开辟我们又有了新的内容new与delete。今天我们来学习C++中的动态内存开辟!

我们先来进行一下内存管理的复习。

目录

C/C++内存分布

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

C++内存管理方式

new/delete操作内置类型

new和delete操作自定义类型

operator new与operator delete函数

new和delete的实现原理

内置类型

 自定义类型

定位new表达式(placement-new)

C++与new的使用场景 


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);
}
1. 选择题:
 选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
 globalVar在哪里?____  staticGlobalVar在哪里?____
 staticVar在哪里?____  localVar在哪里?____
 num1 在哪里?____
 char2在哪里?____  *char2在哪里?___
 pChar3在哪里?____    *pChar3在哪里?____
 ptr1在哪里?____     *ptr1在哪里?____

 globalVar在哪里?C  staticGlobalVar在哪里?C  staticVar在哪里?C  localVar在哪里?A  num1 在哪里?A  char2在哪里?A  *char2在哪里?A  pChar3在哪里?A   *pChar3在哪里?D  ptr1在哪里?A   *ptr1在哪里?C

这些都是上述的答案,全部是关于各种类型的数据在C++中的存放位置。

【说明】
1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
4. 数据段--存储全局数据和静态数据。
5. 代码段--可执行的代码/只读常量。

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

void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}

相信大家对malloc与calloc非常熟悉,唯一的区别就是参数不同,还有就是calloc给予开辟空间初始化,而malloc却没有。realloc是对calloc与malloc进行扩容的,扩容分为异地扩容与原地扩容,当目标位置空间足够时会进行原地扩容,反之如果不够将进行异地扩容。

博主在之前的博客中详细讲解了C语言中的动态内存开辟,如果有疑问可以点击下面链接进行学习:C语言中动态内存管理方式:malloc/calloc/realloc/free icon-default.png?t=N7T8https://blog.csdn.net/m0_74755811/article/details/131820896?spm=1001.2014.3001.5501

C++内存管理方式

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

new/delete操作内置类型

void Test()
{
 // 动态申请一个int类型的空间
 int* ptr4 = new int;
 // 动态申请一个int类型的空间并初始化为10
 int* ptr5 = new int(10);
 // 动态申请10个int类型的空间
 int* ptr6 = new int[3];
 delete ptr4;
 delete ptr5;
 delete[] ptr6;
}

通过上述代码可以看出,使用new进行申请空间非常简单,只需要new加上类型即可。如果我们想进行初始化即可在后面加上(n)即可。当进行开辟多个内存空间时,我们像申请数组一样进行申请即可。但是在多个内存释放时,一定要加上[]。

看到现在我们觉得malloc与new的功能差不多呀,最多就是少些一些字母,那为什么C++还要创造一个新的字符进行学习呢?我们接着往下看:

new和delete操作自定义类型

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}

	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
//还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}

 在内置类型中new与malloc是没有任何区别的,而在自定义类型中就会体现出极大的不同。在自定义类型中malloc不会对开辟的成员对象进行初始化,而new会自动调用构造函数。而在结束时delete会调研析构函数。所以说new与delete关键字就是为C++面向对象而产生的!!!

operator new与operator delete函数

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

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

那我们就会有疑问,new的底层逻辑就是malloc,delete的底层逻辑就是free。那么我们使用new开辟的空间能不能使用free进行释放呢?

答案是:可以,但最好不要交叉使用。因为有时候程序会正常进行,但是有时候就会报错。这是为什么呢?

当我们使用new申请一块非常简单的空间,只有一些基本的变量,最多就是少调用了一层析构函数,并不会影响空间的释放。但是当我们进行比如栈的开辟创建:

class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
		_a = new int[4];
		_top = 0;
		_capacity = 4;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
    Stack st;
    Stack* pst = new Stack;
    delete pst;
    return 0;
}

第一种情况是指针指向在堆开好的空间,只有两层关系,而使用new进行开辟先在堆上开辟对象空间,对象在使用构造函数进行初始化在堆上再开一层空间,是三层的关系。如果我们要使用free进行释放空间,只能将第二层进行释放,而第三层就产生了内存泄漏!!!

所以我们不要交叉使用,做到一一对应!!!

new和delete的实现原理

内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:
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 = 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;
}

C++与new的使用场景 

虽然malloc与new都是在堆上进行开辟空间,但是他们获取内存的方式不一样。malloc是需要多少就索取多少,而new是”提前预支“内存,这样就可以提高new的效率,但是却导致了new空间浪费。所以说有利有弊,我们应该在适当的情况使用适当的做法。

C++中使用malloc和new有不同的用途和行为,你可以根据需要选择哪个更适合你的情况。以下是一些情况下的推荐用法:
使用malloc的情况:

1.C兼容性: 如果你编写的是C++代码,并且需要与C库或其他C代码进行交互,使用malloc可能更合适,因为malloc是C标准库函数。
2.需要手动管理构造和析构: malloc只分配内存,不会自动调用构造函数或析构函数。如果你需要手动控制对象的构造和析构过程,或者分配的内存不是用于存储对象(例如分配原始字节数组),则使用malloc。
3.需要明确指定内存大小: malloc接受一个字节数作为参数,而new会考虑类型的大小和额外的构造函数开销。如果你需要确切控制内存分配的字节数,可以使用malloc。
4.不需要类型检查: new是类型安全的,而malloc不是。如果你需要执行类型不安全的操作,可能需要使用malloc。

使用new的情况:

5.C++对象分配: 如果你需要分配内存以存储C++对象,通常应使用new或new[]。new会自动调用对象的构造函数,new[]用于动态分配数组,并在必要时调用构造函数。
6.类型安全: new提供了类型安全性,可以避免一些常见的内存错误,例如内存泄漏和越界访问。
7.更简洁的语法: new和new[]的语法更简洁,不需要显式指定分配的字节数。
8.自动内存管理: new分配的内存会在对象的生命周期结束时自动释放,从而减少了内存泄漏的风险。

总的来说,如果你编写纯粹的C++代码并需要分配内存以存储对象,通常建议使用new或new[],因为它们提供更好的类型安全性和内存管理。使用malloc通常是在需要更底层的内存分配控制,或者与C代码进行交互时的情况。无论使用哪种方法,都需要谨慎管理内存,确保在不再需要时释放它,以避免内存泄漏。


以上就是本次全部内容,感谢大家观看!!! 

  • 41
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 30
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 30
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

W…Y

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

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

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

打赏作者

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

抵扣说明:

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

余额充值