[C++面试] new、delete相关面试点

一、入门

1、说说new与malloc的基本用途

int* p1 = (int*)malloc(sizeof(int));  // C风格
int* p2 = new int(10);               // C++风格,初始化为10

new 是 C++ 中的运算符,用于在上动态分配内存调用对象的构造函数,会自动计算所需内存大小

#include <iostream>
int main() {int* ptr = new int(5);std::cout << *ptr << std::endl;delete ptr;return 0;
}

 malloc 是 C 语言中的标准库函数,用于在上分配指定大小的内存块,不会调用对象的构造函数,返回的是 void* 类型的指针,需要手动进行类型转换。

int main() {int* ptr = (int*)malloc(sizeof(int));*ptr = 5;std::cout << *ptr << std::endl;free(ptr);return 0;
}

内存分配失败处理malloc返回NULLnew抛出std::bad_alloc异常 

2、delete 和 free 分别用于什么场景?

delete 是 C++ 中的运算符,用于释放由 new 分配的内存,并调用对象的析构函数

#include <iostream>
class MyClass {
public:~MyClass() {std::cout << "Destructor called" << std::endl;}
};
int main() {MyClass* obj = new MyClass();delete obj;return 0;
}

free 是 C 语言中的标准库函数,用于释放由 malloccalloc 或 realloc 分配的内存,不会调用对象的析构函数。 

#include <iostream>
#include <cstdlib>
int main() {int* ptr = (int*)malloc(sizeof(int));free(ptr);return 0;
}

3、new与malloc的关联

new通过调用operator new分配内存,而默认的operator new内部使用malloc

void* operator new(size_t size) {  void* p = malloc(size);  if (!p) throw std::bad_alloc();  return p;  
}  

4、delete NULL或nullptr会发生什么?

操作空指针(NULL/nullptr)​非空指针
delete / delete[]安全(无操作)需确保指针有效且未被重复释放
free安全(无操作)需确保内存由 malloc 分配

底层逻辑​:编译器会检查指针是否为空,若为空则直接跳过析构和内存释放步骤 

最佳实践​:释放后立即置空指针

delete、free并不会把指针置空。

int* p = new int(10);
delete p;  // 第一次释放
delete p;  // 危险!重复释放非空指针

安全性:避免程序员在调用 delete 或 free 前必须显式检查指针是否为空(避免冗余检查)。 

二、进阶

1、new[] 和 delete[] 的作用是什么?

new[] 用于在堆上动态分配数组内存,并对数组中的每个元素调用构造函数。delete[] 用于释放由 new[] 分配的数组内存,并对数组中的每个元素调用析构函数

include <iostream>
class MyClass {
public:MyClass() {std::cout << "Constructor called" << std::endl;}~MyClass() {std::cout << "Destructor called" << std::endl;}
};
int main() {MyClass* arr = new MyClass[3];delete[] arr;return 0;
}

C++ 编译器在解析代码时会忽略 delete 和 [] 之间的所有空白符​(包括空格、换行符、制表符等),推荐 delete[] 的紧凑写法

delete[]arr;
delete []arr;
delete [] arr;
delete[] arr;

2、对于内置数据类型,使用delete、delete[]效果是一样的。这句话对吗?为什么?

内置类型无析构函数​:C++ 的内置数据类型(如 intlong、指针等)没有析构函数。delete 和 delete[] 的核心差异在于是否调用析构函数,而内置类型无需析构,但内存释放的完整性仍取决于运行时环境。某些编译器(如 MSVC)可能通过内存池机制自动回收整个数组内存,但这属于未定义行为,不可依赖。

int* p1 = new int(10);
delete p1;      // 正确释放单个对象
int* p2 = new int[10];
delete[] p2;    // 正确释放数组
delete p2;      // 未定义行为

分配时的元数据记录​:无论是 new 还是 new[],内存分配时系统会记录分配的内存大小和对象数量(存储在 _CrtMemBlockHeader 等结构中)。释放时,delete 和 delete[] 均能通过指针获取这些信息,从而正确释放连续内存块 。

int* p = new int[1000];
delete p;  // 看似正常,但 Valgrind 报告 3996 字节泄漏(1000 * 4 - 4)
+-------------------+
| 数组长度(1000)  |  ← 元信息(通常占用 4/8 字节)
+-------------------+
| 元素0(int)      |  ← 用户可见的指针 `p` 指向此处
+-------------------+
| 元素1(int)      |
+-------------------+
| ...(共 1000 个) |
+-------------------+

3、使用 new 分配内存,用 free 释放会怎么样? 

如果使用 new 分配内存,却用 free 释放,对象的析构函数不会被调用,可能会导致资源泄漏,例如对象中包含动态分配的资源(如文件句柄、网络连接等)无法正确释放。

#include <iostream>
class MyClass {
public:~MyClass() {std::cout << "Destructor called" << std::endl;}
};
int main() {MyClass* obj = new MyClass();free(obj); // 析构函数不会被调用return 0;
}

4、 使用 malloc 分配内存,用 delete 释放会怎么样?

delete 会尝试调用对象的析构函数。 malloc 分配的内存没有经过构造函数初始化,调用析构函数可能会导致未定义行为。

#include <iostream>
#include <cstdlib>class ResourceHolder {
public:ResourceHolder() {std::cout << "ResourceHolder: Acquiring resource..." << std::endl;// 模拟资源获取,例如打开文件、分配内存等resource = new int[100];}~ResourceHolder() {std::cout << "ResourceHolder: Releasing resource..." << std::endl;// 模拟资源释放,例如关闭文件、释放内存等delete[] resource;}private:int* resource;
};int main() {// 使用 malloc 分配内存ResourceHolder* holder = (ResourceHolder*)malloc(sizeof(ResourceHolder));if (holder == nullptr) {std::cerr << "Memory allocation failed!" << std::endl;return 1;}// 尝试使用 delete 释放内存delete holder;return 0;
}
  • 运用 malloc 为 ResourceHolder 对象分配内存。malloc 只是单纯地分配指定大小的内存块,不会调用对象的构造函数,所以 resource 指针不会被正确初始化。
  • 尝试使用 delete 释放内存。delete 会调用对象的析构函数,但是由于 resource 指针未被正确初始化,在析构函数中调用 delete[] resource 就会引发未定义行为,可能会导致程序崩溃或者出现其他不可预测的问题。
    • 当 delete[] resource 尝试释放一个未正确初始化的指针时,可能会访问非法内存地址,从而致使程序崩溃。

5、malloc + delete混用问题

注:newdelete必须成对使用

  • 内存生命周期管理newdelete通过构造函数/析构函数保证对象完整生命周期
  • 混用风险:未调用析构函数(若对象有资源需释放)
class MyClass {int* data;  // 未初始化
public:MyClass() { data = new int[100]; }  // 构造函数未执行!~MyClass() { delete[] data; }       // 析构函数尝试释放野指针
};MyClass* p = (MyClass*)malloc(sizeof(MyClass));
delete p;  // 析构函数调用delete[] data,但data未初始化 → 崩溃

delete确实会调用析构函数,但这一行为是否能正确执行,取决于对象是否被正确构造。通过malloc分配内存时,MyClass的构造函数未被调用,但delete p却尝试调用析构函数。若析构函数中存在对未初始化成员的操作(如delete data),会导致未定义行为​(如访问野指针,引发崩溃)

内置类型(如int)​
无构造函数和析构函数,因此malloc+delete可能不会崩溃(因为没有析构操作),但仍是未定义行为

int* p = (int*)malloc(sizeof(int));
delete p;  // 可能不崩溃,但不符合规范

6、newfree混用

未调用析构函数,且可能因内存布局差异导致崩溃(如new[]的头部信息未处理)

当通过new[]分配数组时,内存布局可能包含头部信息​(记录数组长度),例如:

MyClass* arr = new MyClass[5];
// 内存布局:[长度=5][对象1][对象2]...[对象5]

delete[]会根据头部信息调用5次析构函数,再释放完整内存块。若用free释放,​头部信息未被处理。free 的输入是 arr(指向第一个对象),但 new[] 分配的实际内存块起始地址是 arr - sizeof(头部)。

正确行为:若用户调用 delete[] arr,会从头部地址释放完整内存块(包括头部和所有对象)。​错误行为:若调用 free(arr)free 仅尝试释放从 arr 开始的地址,而实际分配的内存块起始位置未被正确识别,导致部分内存未被释放​(内存泄漏)或堆结构破坏​(可能崩溃)

free(arr) 无法识别 new[] 的内存布局,会释放不完整的地址范围,导致内存泄漏(头部部分对象未被释放),甚至因堆管理器元数据损坏而崩溃。

注:不是只释放了[长度=5][对象1][对象2]...[对象5]

free只能释放通过malloc/calloc/realloc分配的内存块,其底层通过内存块的头部元数据​(如大小信息)来释放整个内存块。

new[]分配的头部信息可能格式与malloc不同,导致free无法正确解析,最终释放的地址范围是未定义的

  • 可能释放不完整free(arr)可能仅释放从对象0地址开始的部分内存(如对象0的存储空间),而头部和其他对象的内存未被释放
  • 可能破坏堆结构:错误释放地址会导致堆管理器元数据损坏,引发后续内存操作崩溃

若编译器未添加头部信息,free(arr) 可能释放整个数组(因内存块连续),但这是未定义行为,依赖具体实现。(内置类型数组

  • 析构函数未被调用 → 资源泄漏。
  • 释放的地址错误(如未回退到头部起始位置)→ 内存布局破坏,可能崩溃

三、高阶

1、如何重载 new 和 delete 运算符?

应用场景

  • 内存池优化(减少碎片)
  • 调试内存泄漏(记录分配/释放日志)
#include <iostream>
#include <cstdlib>
class MyClass {
public:static void* operator new(size_t size) {std::cout << "Custom new operator called" << std::endl;return std::malloc(size);}static void operator delete(void* ptr) {std::cout << "Custom delete operator called" << std::endl;std::free(ptr);}~MyClass() {std::cout << "Destructor called" << std::endl;}
};
int main() {MyClass* obj = new MyClass();delete obj;return 0;
}

2、 如何捕获new过程异常并处理

int main() {try {while (true) {int* ptr = new int[1000000];}} catch (const std::bad_alloc& e) {std::cout << "Memory allocation failed: " << e.what() << std::endl;}return 0;
}

3、 deletedelete[]有何区别?

delete释放单个对象;delete[]释放数组

delete调用一次析构函数;delete[]对数组中每个元素调用析构函数

对数组使用delete会导致内存泄漏或崩溃。

4、new[]如何知道要调用多少次析构函数?

头部信息存储:当分配自定义类型数组时,new[]会在内存块头部额外存储数组长度(如4/8字节)

delete[]根据头部信息确定析构次数,再释放完整内存块

MyClass* arr = new MyClass[5];  
// 内存布局:[长度=5][对象1][对象2]...[对象5]
delete[] arr;  // 读取长度5,调用5次析构函数

内置类型数组:若数组元素是基本类型(如 int),某些编译器可能不添加头部信息(因无需调用析构函数),直接分配连续内存 

 5、设计一个内存泄漏检测工具,如何跟踪new/delete的使用?

重载全局operator new/delete:记录分配/释放的地址和大小。

哈希表跟踪:维护分配记录,检测未配对的newdelete

std::map<void*, size_t> allocMap;  
void* operator new(size_t size) {  void* p = malloc(size);  allocMap[p] = size;  return p;  
}  
void operator delete(void* p) {  if (allocMap.erase(p)) free(p);  else logLeak();  // 检测到未记录释放
}  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/74643.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity URP管线与HDRP管线对比

1. 渲染架构与底层技术 URP 渲染路径&#xff1a; 前向渲染&#xff08;Forward&#xff09;&#xff1a;默认单Pass前向&#xff0c;支持少量实时光源&#xff08;通常4-8个逐物体&#xff09;。 延迟渲染&#xff08;Deferred&#xff09;&#xff1a;可选但功能简化&#…

JDK8卸载与安装教程(超详细)

JDK8卸载与安装教程&#xff08;超详细&#xff09; 最近学习一个项目&#xff0c;需要使用更高级的JDK&#xff0c;这里记录一下卸载旧版本与安装新版本JDK的过程。 JDK8卸载 以windows10操作系统为例&#xff0c;使用快捷键winR输入cmd&#xff0c;打开控制台窗口&#xf…

python爬虫:DrissionPage实战教程

如果本文章看不懂可以看看上一篇文章&#xff0c;加强自己的基础&#xff1a;爬虫自动化工具&#xff1a;DrissionPage-CSDN博客 案例解析&#xff1a; 前提&#xff1a;我们以ChromiumPage为主&#xff0c;写代码工具使用Pycharm&#xff08;python环境3.9-3.10&#xff09; …

07-01-自考数据结构(20331)- 排序-内部排序知识点

内部排序算法是数据结构核心内容,主要包括插入类(直接插入、希尔)、交换类(冒泡、快速)、选择类(简单选择、堆)、归并和基数五大类排序方法。 知识拓扑 知识点介绍 直接插入排序 定义:将每个待排序元素插入到已排序序列的适当位置 算法步骤: 从第二个元素开始遍历…

Go语言-初学者日记(八):构建、部署与 Docker 化

&#x1f9f1; 一、go build&#xff1a;最基础的构建方式 Go 的构建工具链是出了名的轻量、简洁&#xff0c;直接用 go build 就能把项目编译成二进制文件。 ✅ 构建当前项目 go build -o myapp-o myapp 指定输出文件名默认会构建当前目录下的 main.go 或 package main &a…

教程:如何使用 JSON 合并脚本

目录 1. 介绍 2. 使用方法 3. 注意事项 4. 示例 5.完整代码 1. 介绍 该脚本用于将多个 COCO 格式的 JSON 标注文件合并为一个 JSON 文件。COCO 格式常用于目标检测和图像分割任务&#xff0c;包含以下三个主要部分&#xff1a; "images"&#xff1a;图像信息&a…

Java学习总结-缓冲流性能分析

测试用例&#xff1a; 分别使用原始的字节流&#xff0c;以及字节缓冲流复制一个很大的视频。 测试步骤&#xff1a; 在这个分析性能需要一个记录时间的工具&#xff1a;这个是记录1970-1-1 00&#xff1a;00&#xff1a;00到现在的总毫秒值。 long start System.currentT…

流影---开源网络流量分析平台(五)(成果展示)

目录 前沿 攻击过程 前沿 前四章我们已经成功安装了流影的各个功能&#xff0c;那么接下来我们就看看这个开源工具的实力&#xff0c;本实验将进行多个攻击手段&#xff08;ip扫描&#xff0c;端口扫描&#xff0c;sql注入&#xff09;攻击靶机&#xff0c;来看看流影的态感效…

vs环境中编译osg以及osgQt

1、下载 OpenSceneGraph 获取源代码 您可以通过以下方式获取 OSG 源代码: 官网下载:https://github.com/openscenegraph/OpenSceneGraph/releases 使用 git 克隆: git clone https://github.com/openscenegraph/OpenSceneGraph.git 2、下载必要的第三方依赖库 依赖库 ht…

Unity:标签(tags)

为什么需要Tags&#xff1f; 在游戏开发中&#xff0c;游戏对象&#xff08;GameObject&#xff09;数量可能非常多&#xff0c;比如玩家、敌人、子弹等。开发者需要一种简单的方法来区分这些对象&#xff0c;并根据它们的类型执行不同的逻辑。 核心需求&#xff1a; 分类和管…

【C++11】lambda

lambda lambda表达式语法 lambda表达式本质是一个匿名函数对象&#xff0c;跟普通函数不同的是它可以定义在函数内部。lambda表达式语法使用层而言没有类型&#xff0c;所以一般是用auto或者模板参数定义的对象去接收lambda对象。 lambda表达式的格式&#xff1a;[capture-l…

fpga:分秒计时器

任务目标 分秒计数器核心功能&#xff1a;实现从00:00到59:59的循环计数&#xff0c;通过四个七段数码管显示分钟和秒。 复位功能&#xff1a;支持硬件复位&#xff0c;将计数器归零并显示00:00。 启动/暂停控制&#xff1a;通过按键控制计时的启动和暂停。 消抖处理&#…

《UNIX网络编程卷1:套接字联网API》第6章 IO复用:select和poll函数

《UNIX网络编程卷1&#xff1a;套接字联网API》第6章 I/O复用&#xff1a;select和poll函数 6.1 I/O复用的核心价值与适用场景 I/O复用是高并发网络编程的基石&#xff0c;允许单个进程/线程同时监控多个文件描述符&#xff08;套接字&#xff09;的状态变化&#xff0c;从而高…

SpringBoot+vue前后端分离整合sa-token(无cookie登录态 详细的登录流程)

SpringBootvue前后端分离整合sa-token&#xff08;无cookie登录态 & 详细的登录流程&#xff09; 1.介绍sa-token1.1 框架定位1.2 核心优势 2.如何整合sa-token3.如何进行无cookie模式登录3.1后端3.1.1 VO层3.1.2 Controller层3.1.3 Service层 3.2前端3.2.1 登录按钮自定义…

MYOJ_1171:(洛谷P1075)[NOIP 2012 普及组] 质因数分解(数学相关,质数与约数基础)

题目描述 已知正整数 n 是两个不同的质数的乘积&#xff0c;试求出两者中较大的那个质数。 1≤n≤210^9 输入 输入一个正整数 n。 输出 输出一个正整数 p&#xff0c;即较大的那个质数。 样例输入输出 输入&#xff1a;21 输出&#xff1a;7 思路: 为了节约时间与…

Python语言的测试用例设计

Python语言的测试用例设计 引言 随着软件开发的不断进步&#xff0c;测试在软件开发生命周期中的重要性日益凸显。测试用例设计是软件测试的核心&#xff0c;它为软件系统的验证和验证提供了实施的基础。在Python语言中&#xff0c;由于其简洁明了的语法和强大的内置库&#…

SpringKafka消息消费:@KafkaListener与消费组配置

文章目录 引言一、Spring Kafka消费者基础配置二、KafkaListener注解使用三、消费组配置与负载均衡四、手动提交偏移量五、错误处理与重试机制总结 引言 Apache Kafka作为高吞吐量的分布式消息系统&#xff0c;在大数据处理和微服务架构中扮演着关键角色。Spring Kafka为Java开…

VMware 虚报化Ubuntu 卡成一B,如何接招?

故事背景 Win10 专业版 安装VMware pro ,虚拟化出一个Window10&#xff0c;另一个是UBuntu.自从使用起来去不去就卡死。开始是以为驱动或者升级造成的&#xff0c;重新安装一段时间问题照旧。更气人的这种现象具有不定期性&#xff0c;说不定什么时候就来这么一出。 直接解决方…

cloud项目批量修改主机号

当clone了一个cloud项目后&#xff0c;要把别人的主机号全部改成自己的&#xff0c;非常麻烦 在项目根目录下&#xff0c;启动 Git Bash。在 Git Bash 终端中使用原始的 Unix 命令&#xff1a; find . -type f -exec sed -i s/127\.0\.0\.1/132.168.190.163/g {} 其中127.0.…

微信小程序使用 Vant Weapp 组件库教程

在微信小程序项目中使用 Vant 组件库&#xff08;Vant Weapp&#xff09;主要包括以下几个步骤&#xff1a; 1. 初始化项目并安装 Vant Weapp 初始化 npm 在项目根目录下运行以下命令&#xff0c;生成 package.json&#xff1a; npm init -y安装 Vant Weapp 执行以下命令安装 V…