13.C++内存管理2(C++ new和delete的使用和原理详解,内存泄漏问题)
⭐本篇重点:new, delete的使用和原理
⭐本篇代码:c++学习/04.c++-动态内存管理 · 橘子真甜/c++-learning-of-yzc - 码云 - 开源中国 (gitee.com)
目录
一. new和delete的使用
1.1 操作内置类型
1.2 操作自定义类型
二. new, delete与malloc, free的区别
2.1 相同点
2.2 不同点
三. new/delete原理(operator new 和 operator delete)
3.1 函数operator new / operator delete
3.2 new 和 delete 原理
四. 内存泄漏问题
4.1 什么是内存泄漏?内存泄漏的危害是什么?
4.2 内存泄漏的原因
4.3 避免内存泄漏
五. 下篇文章:STL简介
一. new和delete的使用
new和delete是C++提供的一对操作符,可以完成对内存的管理。常用于完成内置类型和自定义类型的初始化
其中,new用于开辟空间,delete使用释放空间
1.1 操作内置类型
#include <iostream>
using namespace std;
int main()
{
//1.new申请一个int型的变量,并将其赋值为999
int* a = new int(999);
//2.new申请10个int型的变量
int* nums = new int[10];
for (int i = 0; i < 10; i++)
{
nums[i] = i;
}
cout << *a << endl;
for (int i = 0; i < 10; i++)
{
cout << nums[i] << " ";
}
//释放申请的空间
delete a;
delete[] nums;
return 0;
}
分析:我们申请了并初始化一个变量a,以及一个数组nums。然后输出它们的值。运行结果如下:
要注意new和delete的搭配使用
new和delete用于开辟和释放一个元素。
new 类型[] 和 delete[] 用于开辟和释放连续的空间。
1.2 操作自定义类型
new和delete还能够操作自定义类型。
new初始化自定义类型的时候会调用其 构造函数
delete释放自定义类型的时候会调用其 析构函数
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "调用A的构造函数" << endl;
}
~A()
{
cout << "调用A的析构函数" << endl;
}
void print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p1 = new A;
p1->print();
A* p2 = new A(1);
p2->print();
A* p3 = new A[5]; //初始化连续的变量
delete p1;
delete p2;
delete[] p3;
return 0;
}
运行结果如下:
二. new, delete与malloc, free的区别
2.1 相同点
new/delete 与 malloc/free 都是在堆上开辟空间,并且需要用户手动释放空间
2.2 不同点
new/delete是C++提供的操作符,malloc/free是C语言提供的库函数
new/delete申请空间会对其进行初始化,malloc/free不会进行初始化
new/delete申请空间会调用其构造函数,释放空间会调用析构函数,malloc/free 申请释放空间不会调用构造函数和析构函数
举例:
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "调用A的构造函数" << endl;
}
~A()
{
cout << "调用A的析构函数" << endl;
}
void print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
//new/delete申请空间会初始化
cout << "new/delete" << endl;
A* p1 = new A;
delete p1;
//malloc/free申请空间不会初始化
cout << "malloc/free" << endl;
A* p2 = (A*)malloc(sizeof(A));
if (NULL == p2)
{
cout << "malloc error!" << endl;
exit(1);
}
free(p2);
p2 = NULL;
return 0;
}
malloc申请空间的时候,我们需要将返回的 void* 强制转化为我们需要的类型,并且需要计算我们需要申请的空间,还需要处理返回值为NULL的情况。
new申请空间不需要强转,不许计算大小直接在后面放我们需要申请的空间类型即可,申请多个变量带上[]即可!不过使用new的时候需要捕获异常
三. new/delete原理(operator new 和 operator delete)
3.1 函数operator new / operator delete
new/delete是用于动态申请和释放空间的操作符,而operator new 和 operator delete是系统提供的全局函数
new在底层调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间。
operator new 和 operator delete的部分源码如下
//operator new: 该函数通过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来申请空间的。申请失败的时候会抛出异常,operator delete是通过free来实现释放空间的
3.2 new 和 delete 原理
对于内置类型,new/delete和malloc/free相似。区别见本篇2.2
对于自定义类型:
new原理:
调用函数 operator new 申请空间,然后在申请的空间上调用构造函数完成对象的构造和初始化
delete原理:
先在申请的空间上调用对象的析构函数进行清理,然后调用函数 operator free 进行释放对象的空间
四. 内存泄漏问题
4.1 什么是内存泄漏?内存泄漏的危害是什么?
内存泄漏是因为程序员的疏忽和错误,没有去释放不再使用的内存空间。内存泄漏不是物理内存的消失,而是应用程序分配某段内存后,因为错误设计,导致程序失去了对这段内存的控制,造成空间浪费
内存泄漏的危害:长期运行的程序出现内存泄漏会导致系统越来越卡,直到卡死。
4.2 内存泄漏的原因
内存泄漏一般分为堆内存泄漏和系统资源泄漏。
堆:当我们使用malloc/new申请空间之后,使用完了却没有使用delete/new去释放这段空间。结果就是这段空间无法使用
系统资源泄露:指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放 掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
4.3 避免内存泄漏
1 要有良好的工程设计和编码规范,申请完空间之后需要匹配释放空间。
2 有时候我们写了释放空间的代码,由于发生了异常导致这段代码没有被执行。这种时候就需要采用RAII思想和智能指针来解决
3 使用内存泄漏检测工具进行检测