内存管理--《Hello C++ Wrold!》(8)--(C/C++)--深入剖析new和delete的使用和底层实现
文章目录
- 前言
- C/C++内存分布
- new和delete
- new和delete的底层
- 定位new表达式
- 内存泄漏
- 作业部分
前言
在C/C++编程中,内存管理是理解程序运行机制的核心基础,也是开发高效、稳定程序的关键。无论是局部变量的存储、动态内存的分配,还是对象生命周期的管理,都与内存的合理使用密切相关。
本内容将围绕C/C++内存分布、动态内存操作(new/delete
与malloc/free
)、内存泄漏等核心概念展开,通过理论解析与实例分析相结合的方式,帮助读者深入理解以下关键内容:
- 程序运行时内存的逻辑分区(栈区、堆区、静态区、常量区等)及其数据存储特性;
new/delete
与malloc/free
的本质区别,以及在自定义类型场景下的关键差异;- 动态内存分配的底层实现原理与定位
new
表达式的特殊应用; - 内存泄漏的成因与典型案例分析,为后续学习智能指针、内存检测工具等进阶内容奠定基础。
此外,内容中还包含典型例题与面试考点,通过具体场景帮助读者强化理解,掌握内存管理的实践技巧与常见问题排查方法。无论是编程初学者还是希望巩固基础的开发者,均可通过本文系统梳理C/C++内存管理的核心知识体系。
C/C++内存分布
栈区存放局部数据,如函数参数使用的空间
堆区存放动态开辟的空间
静态区存放静态数据/全局变量
常量区存放常量数据
注意:局部的静态变量也存在静态区,const不会改变应该存在哪
判断两个变量所在的区是不是同一个的一个小技巧:看他们的地址相差大不大
new和delete
回顾:malloc,calloc,realloc的区别
malloc就是分配内存
calloc是分配内存加上把内存中的值搞成0
realloc就是扩大或者缩小已经分配的内存
new/delete和malloc/free的区别:(简略)
1.对于动态申请内置类型的数据:
new/malloc除了用法上面,其他方面没有什么区别
2.对于动态申请自定义类型的数据:
new/malloc除了用法上面,还有一个区别就是:new会调用构造函数区进行初始化,delete会进行调用析构函数进行清理
new如果开辟失败的话,会抛异常,需要用try catch去抓获
注意:new和delete malloc和free要配套使用,不能混用;不然有些编译器会报错
引申:抛异常不抓获的话会弹窗报错,抓获的话会温柔些
new/delete和malloc/free的区别(面试常考)(详细):
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在释放空间前会调用析构函数完成空间中资源的清理
这种的记忆方法:记大点(有哪些方面),然后用自己的话说出来(当然也可以先搞个自己的话的模板)
new和delete的用法:
// 申请和释放一个int类型的空间
int* ptr4 = new int; delete ptr4;
// 申请和释放一个int类型的空间并初始化为10
int* ptr5 = new int(10); delete ptr5;
// 申请和释放10个int类型的空间
int* ptr6 = new int[10]; delete[] ptr6;
这种要初始化的话,需要int* ptr6 = new int[10]{1,2,3,4};
这样,没初始化的部分会搞成类型的默认值class A
{
public:
A(int a = 0,int b = 0)
: _a(a)
,_b(b){}
private:
int _a; int_b;
};
main函数里面:A* p = new A(1,2); delete p;
当然A* p = new A;也可以,因为有默认构造函数
也可以这样:A*p1 = new A[3]{A(1,1),A(2)};这样//缺省的就按默认构造函数来搞了
//这样搞的话,也会让匿名对象的生命周期延长class A
{
public:
A(int a = 0)
: _a(a)
{}
private:
int _a;
};
单个参数的话,main函数里面这样也行
A*p1 = new A[3]{1,2};
new和delete的底层
new的话是先开空间,再调用构造函数
这里的开空间:里面用了 operator new->operator new的底层又是malloc
delete是先调用析构函数再释放空间
这里的释放空间:里面用了operator delete->它的底层又是free
eg:new[]T这种也类似,无非就是同时对N个对象搞
对这里的析构函数和构造函数使用的理解:
由构造函数生成的那部分才是由析构函数清理的
注意:不能重复delete和free(除非每次搞完都搞成nullptr)
也不能delete和free一个未初始化过的指针
定位new表达式
用途:一般是配合内存池使用的
使用格式: new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表用法:class A { public: A(int a = 0) : _a(a) {} private: int _a; };A* p1 = (A*)malloc(sizeof(A));//p1指向的还不是对象,因为没执行构造函数 new(p1)A; // 注意:如果A类的构造函数需要传参数时,此处需要传参 //这里不是匿名对象,是用定位new调用的构造函数,也就是显式调用构造函数 p1->~A();//理解 free(p1); 或者用operator new来申请空间..... A* p2 = (A*)operator new(sizeof(A)); new(p2)A(10);//这样传参过去也行 p2->~A();//显示调用 operator delete(p2);
本质:其实就是把new和delete拆开来搞了
内存泄漏
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。 – 详细的方案等到以后再讲
作业部分
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在哪里?---->C
staticVar在哪里?---->C
num1 在哪里?---->A!!!
char2在哪里?---->A
*char2在哪里?---->A//首元素的地址,还是在栈
pChar3在哪里?---->A
*pChar3在哪里?---->D//注意和*char的区别,*pChar指向的是常量区那个
ptr1在哪里?---->A
*ptr1在哪里?---->B
注意:eg:*ptr1和&ptr1要区分
sizeof(char2) = 5; strlen(char2) = 4;
//strlen不算'\0'
注意是\0不是/0
下面有关c++内存分配堆栈说法错误的是(D)
A.对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制
B.对于栈来讲,生长方向是向下的,也就是向着内存地址减小的方向;对于堆来讲,它的生长方向是向上的,是向着内存地址增加的方向增长
C.对于堆来讲,频繁的 new/delete 势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题
D.一般来讲在 32 位系统下,堆内存可以达到4G的空间,但是对于栈来讲,一般都是有一定的空间大小的D:32位系统下,最大的访问内存空间为4G,所以不可能把所有的内存空间当做堆内存使用
C++中关于堆和栈的说法,哪个是错误的:(C)
A.堆的大小仅受操作系统的限制,栈的大小一般较小
B.在堆上频繁的调用new/delete容易产生内存碎片,栈没有这个问题
C.堆和栈都可以静态分配
D.堆和栈都可以动态分配堆只能动态分配,栈可以用函数_alloca进行动态分配
ClassA *pclassa=new ClassA[5];
delete pclassa;
c++语言中,类ClassA的构造函数和析构函数的执行次数分别为(D)
A.5,1
B.1,1
C.5,5
D.程序可能崩溃原因:申请对象数组,会调用构造函数5次,delete由于没有使用[];此时只会调用一次析构函数,但往往会引发程序崩溃
如果是内置类型的话,不会崩溃:eg:
使用 char* p = new char[100]申请一段内存,然后使用delete p释放//也不会有内存泄漏
//因为内置类型不需要调用析构函数
设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数调用顺序为? (ABDC)
C c;
void main()
{A*pa=new A();B b;static D d;delete pa;
}