这里写目录标题
前言
C++是一个极度追求性能的语言,因此在所有语言之中,对于内存过分执着“较真”,这也就产生了内存管理!
1.C/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";
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);
}
通过这段代码,来考验考验我们的C语言内存管理问题:
选择题:
选项: A.栈 B.堆 C.数据段 D.代码段
globalVar在哪里?C staticGlobalVar在哪里?_C
staticVar在哪里?C localVar在哪里?A
num1 在哪里?A
char2在哪里?A *char2在哪里?A
pChar3在哪里?A *pChar3在哪里?D
ptr1在哪里?A *ptr1在哪里?B
1.栈又被称之为堆栈,非静态局部变量/函数参数/返回值等等都存放在栈之中。
2.内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库,可使用系统接口创建共享内存,做进程间通信。
3.堆用域程序运行时动态内存的分配。
4.数据段是存储全局数据和静态数据的。
5.代码段是放置可执行的代码和只读常量。
2. C/C++语言动态内存管理方式
2.1C语言动态内存管理方式
malloc/calloc/realloc/free
是我们C语言中经常使用到的动态内存管理,
1.普通的数组空间申请
void test1()
{
int array[10]; //都为随机值
int array2[10] = { 1, 2, 3 }; //除过前三个,其它都为0
int array3[10] = { 0 }; //所有位置都为0
}
2. malloc和calloc以及realloc的空间管理
void test2()
{
//malloc:只进行空间申请,不进行初始化
int* ptr = (int*)malloc(sizeof(int));
*ptr = 4;
//calloc: 进行空间申请 + 零初始化
int* ptr2 = (int*)calloc(1, sizeof(int));
//realloc: 第一个参数为nullptr/NULL, 功能等价于malloc
int* ptr3 = (int*)realloc(nullptr, sizeof(int));
//调整空间大小:
// 1. 直接原地调整大小
// 2. 重新开空间: 重新申请空间,内容拷贝,释放原有空间
int* ptr4 = (int*)realloc(ptr, sizeof(int) * 4);
char* ptr5 = (char*)realloc(ptr2, sizeof(char));
free(ptr3);
free(ptr4);
free(ptr5);
//传入realloc中的空间后续不需要显式释放,会导致二次释放的问题
/*free(ptr);
free(ptr2);*/
}
2.或者realloc
2.2C++动态内存管理方式
虽然C语言的内存管理方式在C++中可以继续使用,但是有些地方不仅无能为力而且使用起来还会有很多缺点,因此C++也是提出了适合自己的内存管理方式,那就是new和delete操作符来进行动态内存管理。
void test1()
{
// 单个类型的空间:new + 类型
// 连续空间:new + 类型[个数]
// 单个类型空间申请 + 初始化: new + 类型(初始值)
// 基本类型用new申请连续空间,不能初始化
int* ptr3 = new int;
int* ptr4 = new int[10];
int* ptr5 = new int(5); //初始化为5
//释放空间
//单个空间: delete 指针
//连续空间: delete[] 指针
//申请和释放的操作匹配使用: malloc free, new delete, new [] delete[]
delete ptr3;
delete ptr5;
delete[] ptr4;
}
class Date{
public:
Date(){
}
}
void test2()
{
//动态创建自定义类型的对象:
//new:动态开空间 + 调用构造函数初始化
//申请单个空间: new 自定义类型(参数列表)
Date* pd = new Date(2020);
Date* pd2 = new Date(2030);
Date* pd4 = new Date; //调用默认构造:无参,全缺省
//申请连续的空间:new 自定义类型[个数], 自动调用默认构造进行初始化,如果没有默认构造,编译器报错
Date* pd3 = new Date[10];
//释放自定义类型的空间
//delete: 调用析构函数清理资源 + 释放空间
delete pd;
delete pd2;
delete pd4;
//连续空间: 调用N次析构 + 释放空间
delete[] pd3;
}
区别:在申请自定义类型的空间时:new会调用构造函数,而delete会调用析构函数,而malloc和free不会。
3. operator new和operator delete
operator new和operator delete这两者是系统所提供的全局函数,new会在底层调用operator new来申请空间,delete会在底层调用operator delete来释放空间,而在operator new和operator delete 实现中我们可以发现还是通过malloc来进行申请,通过free来进行释放。
void test()
{
//void* operator new(size_t n): 不是运算符重载函数,而是一个全局函数
// : 使用方式和malloc类似
// : 封装malloc + 异常
//new 10;
//new的执行过程(自定义类型):operator new --> malloc --> 构造函数
char* ptr = (char*) operator new(sizeof(char));
char* ptr2 = (char*)malloc(sizeof(char));
//void operator delete(void* ptr):不是运算符重载函数,而是一个全局函数
// :使用方式和free类似
// :封装free
// delete执行过程(自定义类型): 析构 --> operator delete --> free
operator delete(ptr);
free(ptr2);
free(nullptr);
operator delete(nullptr);
}
4.new和delete的实现原理
5new的表达式
void test()
{
//new定位表达式,给予申请的空间进行初始化
Date* pd = (Date*)malloc(sizeof(Date));
//new定位表达式: new (地址) 类型(参数列表)
// :在已经开好的空间上显式调用构造函数
new (pd)Date(2030);
Date* pd2 = (Date*)malloc(sizeof(Date));
new (pd2)Date;
}
6面试题
1.malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
malloc和free是函数,new和delete是操作符
malloc申请的空间不会初始化,new可以初始化
malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
2,什么是内存泄漏,内存泄漏的危害,如何避免内存泄漏
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
避免内存泄漏:实现防御如智能指针;时候差错如泄漏检测工具。
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}