目录
3.3 operator new与operator delete函数
一、C/C++内存分布
我们先来简单回顾一下:C/C++内存区域的划分:
内核空间 (用户不能读写) |
栈 (向下增长) |
内存映射段 (文件映射、动态库、静态映射) |
堆 (向上增长) |
数据段 (全局数据、静态数据) |
代码段 (可执行代码/只读常量) |
【说明】
1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
4. 数据段--存储全局数据和静态数据。
5. 代码段--可执行的代码/只读常量
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在哪里?____ staticVar在哪里?____ num1 在哪里?____staticGlobalVar在哪里?____localVar在哪里?____
char2在哪里?____ pChar3在哪里?____ ptr1在哪里?____*char2在哪里?___*pchar3在哪里?____*ptr1在哪里?____
2. 填空题:
sizeof(num1) = ____;sizeof(char2) = ____; sizeof(pChar3) = ____; sizeof(ptr1) = ____;
3. sizeof 和 strlen 区别?
答案如下:
选择答案:
CCACA AAAAD B填空答案:
40 5 4/8 4/8
问答答案:
sizeof会返回\0,strlen不会返回\0。
上述问题中大部分问题都没啥问题,但对于选择题 *char2与*pchar3的答案可能会有疑惑,这里简单说一下:
char2 是一个数组,对数组进行解引用就是访问其第一个元素的地址。
pchar3 是指针,对其进行解引用就是访问它地址所对应的值。
二、内存管理方式
3.1 C语言中动态内存管理方式
经过前面的学习我们知道:在C语言中管理内存靠的是:malloc、calloc、realloc、free来进行管理的,其具体用法,可自行前去复习,这里不过多解释。
我们明白C++兼容C语言这也就意味着:我们可以通过用C语言的方式来管理C++中的内存,这种方式虽然可以,但总觉得怪怪的,那么,C++中是否有自己的一套管理机制呢?当然有,听我慢慢道来:
3.2 C++内存管理方式
在C++中内存管理靠的是new与delete来进行,其使用方法如下:
void Test()
{
// 动态申请一个int类型的空间
int* ptr1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
// 动态申请10个int类型的空间
int* ptr3 = new int[3];
delete ptr1;
delete ptr2;
delete[] ptr3;
//对于申请多个空间的,要采取以上方式进行释放
}
这个是内置类型,那么对于自定义类型可行不可行呢?当然可以,如下:
class MyClass
{
public:
MyClass(int a = 10)
:_a(a)
{
cout << "MyClass()" << endl;
};
~MyClass()
{
cout << "~MyClass()" << endl;
};
private:
int _a;
};
int main()
{
MyClass* a1 = (MyClass*)malloc(sizeof(MyClass));
MyClass* a2 = new MyClass(1);
free(a1);
delete a2;
int* a3 = (int*)malloc(sizeof(int));
int* a4 = new int;
free(a3);
delete a4;
MyClass* a5 = (MyClass*)malloc(sizeof(MyClass) * 10);
MyClass* a6 = new MyClass[10];
free(a5);
delete[] a6; // 先 10次析构 再去调用 operator delete[]
return 0;
}
通过打印或汇编我们会发现,new与delete会自动调用构造与析构,而malloc与free则不会这样干。
这时有人就要问了:我发现:new使用时会有operator new,delete使用时会有operator delete 它们有什么联系吗?这里就进入到了我们的重点内容了。
3.3 operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是 系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间。
其底层调用顺序为:
VS2022编译器 对其实现如下:
_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(
size_t _Size
);
_NODISCARD _Ret_maybenull_ _Success_(return != NULL) _Post_writable_byte_size_(_Size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(
size_t _Size,
::std::nothrow_t const&
) noexcept;
_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(_Size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](
size_t _Size
);
_NODISCARD _Ret_maybenull_ _Success_(return != NULL) _Post_writable_byte_size_(_Size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](
size_t _Size,
::std::nothrow_t const&
) noexcept;
void __CRTDECL operator delete(
void* _Block
) noexcept;
void __CRTDECL operator delete(
void* _Block,
::std::nothrow_t const&
) noexcept;
void __CRTDECL operator delete[](
void* _Block
) noexcept;
void __CRTDECL operator delete[](
void* _Block,
::std::nothrow_t const&
) noexcept;
void __CRTDECL operator delete(
void* _Block,
size_t _Size
) noexcept;
void __CRTDECL operator delete[](
void* _Block,
size_t _Size
) noexcept;
这上面代码看的云里雾里的,总结就是:
operator new 实际也是通过malloc来申请空间,如果 malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施 就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
3.4 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来释 放空间
三、内存泄漏
什么是内存泄漏:
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
解决方案:
1、事前预防型。如智能指针等。
2、事后查错型。如泄 漏检测工具。
完!