当前位置: 首页 > news >正文

内存管理--《Hello C++ Wrold!》(8)--(C/C++)--深入剖析new和delete的使用和底层实现

文章目录

  • 前言
  • C/C++内存分布
  • new和delete
    • new和delete的底层
    • 定位new表达式
  • 内存泄漏
  • 作业部分

前言

在C/C++编程中,内存管理是理解程序运行机制的核心基础,也是开发高效、稳定程序的关键。无论是局部变量的存储、动态内存的分配,还是对象生命周期的管理,都与内存的合理使用密切相关。

本内容将围绕C/C++内存分布、动态内存操作(new/deletemalloc/free)、内存泄漏等核心概念展开,通过理论解析与实例分析相结合的方式,帮助读者深入理解以下关键内容:

  • 程序运行时内存的逻辑分区(栈区、堆区、静态区、常量区等)及其数据存储特性;
  • new/deletemalloc/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;
}

相关文章:

  • Linux--进程概念
  • 源码解析(三):Stable Diffusion
  • 嵌入式学习笔记 - freeRTOS 动态创建任务跟静态创建任务的区别,以及内存回收问题
  • 【AI智能体】Spring AI MCP 从使用到操作实战详解
  • 概率统计:AI大模型的数学支柱
  • 【HW系列】—安全设备介绍(开源蜜罐的安装以及使用指南)
  • 做好 4个基本动作,拦住性能优化改坏原功能的bug
  • 数据库系统概论(十二)SQL 基于派生表的查询 超详细讲解(附带例题表格对比带你一步步掌握)
  • 【C#】Quartz.NET怎么动态调用方法,并且根据指定时间周期执行,动态配置类何方法以及Cron表达式,有请DeepSeek
  • 智启未来:当知识库遇见莫奈的调色盘——API工作流重构企业服务美学
  • 我爱学算法之—— 前缀和(上)
  • 分布式锁剖析
  • STM32 笔记 _《GPIO配置从低层走向高层》
  • 盲盒经济2.0:数字藏品开箱是否适用赌博法规
  • 牛客2025年儿童节比赛
  • 不使用绑定的方法
  • 打卡day42
  • 计算机网络技术
  • vscode编辑器怎么使用提高开发uVision 项目的效率,如何编译Keil MDK项目?
  • 28 C 语言作用域详解:作用域特性(全局、局部、块级)、应用场景、注意事项
  • ssh框架做音乐网站/推广官网
  • 心得网站建设/seo云优化方法
  • 含山县城市建设有限公司网站/企业门户网站模板
  • 免费网站空间怎么/湖北网站seo
  • 网站建设123/seo主管招聘
  • 企业网站托管常见问题/web设计一个简单网页