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

【C++面试题】malloc和new delete和delete[]

文章目录

  • 一、malloc和new
      • 1. `malloc` 的底层实现
        • 1.1 内存管理
        • 1.2 系统调用
        • 1.3 内存对齐
      • 2. `new` 的底层实现
        • 2.1 内存分配
        • 2.2 构造函数调用
        • 2.3 异常处理
      • 3. `malloc` 和 `new` 的区别
      • 4. 总结
  • 二、delete和delete[]
      • 1. `delete` 和 `delete[]` 的区别
        • 1.1 `delete` 的行为
        • 1.2 `delete[]` 的行为
      • 2. 错误使用 `delete` 和 `delete[]` 的后果
        • 2.1 使用 `delete` 释放 `new[]` 分配的数组
        • 2.2 使用 `delete[]` 释放 `new` 分配的单个对象
      • 3. 底层机制
        • 3.1 `new` 和 `delete` 的底层
        • 3.2 `new[]` 和 `delete[]` 的底层
        • 3.3 内存布局示例
      • 4. 如何避免错误
        • 4.1 匹配使用
        • 4.2 使用智能指针
      • 5. 总结

在这里插入图片描述

一、malloc和new

1. malloc 的底层实现

malloc 是 C 标准库中的函数,用于在堆上分配指定大小的内存块。其底层实现通常依赖于操作系统的内存管理机制。

1.1 内存管理
  • 堆内存管理malloc 从堆(heap)中分配内存。堆是一个由操作系统管理的内存区域,程序可以通过 mallocfree 来动态分配和释放内存。
  • 内存块管理malloc 通常会维护一个空闲内存块链表(free list),用于跟踪哪些内存块是空闲的。当调用 malloc 时,它会遍历这个链表,找到一个足够大的空闲块来满足请求。
1.2 系统调用
  • brksbrk:在某些系统中,malloc 使用 brksbrk 系统调用来调整堆的大小。这些系统调用会改变程序的“break”指针,从而扩展或收缩堆的大小。
  • mmap:对于较大的内存分配,malloc 可能会使用 mmap 系统调用,直接将内存映射到进程的地址空间。这种方式可以避免频繁调整堆的大小。
1.3 内存对齐
  • malloc 通常会返回对齐的内存块,以满足硬件或操作系统的对齐要求。例如,在 64 位系统上,malloc 可能会返回 8 字节对齐的内存。

2. new 的底层实现

new 是 C++ 中的操作符,用于动态分配内存并调用对象的构造函数。它的底层实现通常依赖于 malloc 或类似的内存分配机制。

2.1 内存分配
  • 调用 malloc:在大多数实现中,new 操作符会调用 malloc 来分配内存。new 不仅分配内存,还会调用对象的构造函数来初始化对象。
  • 内存大小new 会根据对象的大小来分配内存,通常还会额外分配一些内存用于存储元数据(如对象的大小或类型信息)。
2.2 构造函数调用
  • 对象初始化new 操作符在分配内存后,会自动调用对象的构造函数来初始化对象。这是 newmalloc 的主要区别之一。
2.3 异常处理
  • 异常抛出:如果内存分配失败,new 会抛出 std::bad_alloc 异常(除非使用了 nothrow 版本的 new)。这与 malloc 不同,malloc 在分配失败时返回 NULL

3. mallocnew 的区别

特性mallocnew
语言CC++
内存分配仅分配内存分配内存并调用构造函数
内存释放freedelete
失败处理返回 NULL抛出 std::bad_alloc 异常
内存对齐通常对齐到最大基本类型大小通常对齐到对象的最大对齐要求
内存大小需要手动计算自动计算对象大小

4. 总结

  • malloc 是 C 标准库中的函数,底层依赖于操作系统的内存管理机制(如 brksbrkmmap),主要用于分配原始内存块。
  • new 是 C++ 中的操作符,底层通常调用 malloc 来分配内存,并自动调用对象的构造函数来初始化对象。

两者在内存分配和管理上有相似之处,但 new 提供了更高层次的功能,特别是在对象构造和异常处理方面。

在 C++ 中,deletedelete[] 是用于释放动态分配内存的操作符,但它们的行为和用途有所不同。正确使用它们非常重要,否则可能导致未定义行为(undefined behavior)。以下是对 deletedelete[] 的详细说明,以及错误使用它们(如 newdelete[] 混用)的后果。


二、delete和delete[]

1. deletedelete[] 的区别

操作符用途适用场景
delete释放单个对象的内存,并调用单个对象的析构函数。用于释放 new 分配的对象。
delete[]释放对象数组的内存,并依次调用数组中每个对象的析构函数。用于释放 new[] 分配的数组。
1.1 delete 的行为
  • 用于释放通过 new 分配的单个对象。
  • 调用对象的析构函数。
  • 释放对象占用的内存。
1.2 delete[] 的行为
  • 用于释放通过 new[] 分配的对象数组。
  • 依次调用数组中每个对象的析构函数。
  • 释放整个数组占用的内存。

2. 错误使用 deletedelete[] 的后果

2.1 使用 delete 释放 new[] 分配的数组
int* arr = new int[10];
delete arr;  // 错误:应该使用 delete[]
  • 后果
    • 只会调用第一个元素的析构函数(如果元素是类对象)。
    • 内存释放不完整,可能导致内存泄漏。
    • 未定义行为(undefined behavior),程序可能崩溃或产生不可预测的结果。
2.2 使用 delete[] 释放 new 分配的单个对象
int* p = new int;
delete[] p;  // 错误:应该使用 delete
  • 后果
    • 程序会尝试释放一个不存在的数组,可能导致堆损坏。
    • 未定义行为(undefined behavior),程序可能崩溃或产生不可预测的结果。

3. 底层机制

3.1 newdelete 的底层
  • newdelete 的实现通常依赖于 mallocfree
  • new 不仅分配内存,还会调用构造函数。
  • delete 不仅释放内存,还会调用析构函数。
3.2 new[]delete[] 的底层
  • new[] 会分配额外的内存来存储数组的大小(通常是在数组开头),以便 delete[] 知道需要调用多少次析构函数。
  • delete[] 会根据数组大小依次调用每个元素的析构函数,然后释放整个数组的内存。
3.3 内存布局示例

假设有一个类 MyClass,使用 new[] 分配数组:

MyClass* arr = new MyClass[10];

内存布局可能如下:

[数组大小(10)][MyClass 对象 1][MyClass 对象 2]...[MyClass 对象 10]
  • delete[] 会从数组大小中读取值(10),然后依次调用 10 个对象的析构函数,最后释放整个内存块。

如果错误地使用 delete

  • 程序只会尝试释放第一个对象的内存,而不会释放整个数组,导致内存泄漏和未定义行为。

4. 如何避免错误

4.1 匹配使用
  • 使用 new 分配的内存,必须用 delete 释放。
  • 使用 new[] 分配的内存,必须用 delete[] 释放。
4.2 使用智能指针
  • 使用 std::unique_ptrstd::shared_ptr 可以避免手动管理内存。
  • 对于数组,可以使用 std::unique_ptr<T[]>std::vector

示例:

// 使用 std::unique_ptr 管理单个对象
std::unique_ptr<MyClass> p(new MyClass);

// 使用 std::unique_ptr 管理数组
std::unique_ptr<MyClass[]> arr(new MyClass[10]);

// 使用 std::vector 管理数组
std::vector<MyClass> vec(10);

5. 总结

操作符正确使用场景错误使用场景后果
delete释放 new 分配的单个对象释放 new[] 分配的数组内存泄漏、未定义行为
delete[]释放 new[] 分配的数组释放 new 分配的单个对象堆损坏、未定义行为
  • 始终匹配使用newdeletenew[]delete[]
  • 优先使用智能指针:避免手动管理内存,减少错误发生的可能性。

http://www.kler.cn/a/530492.html

相关文章:

  • LeetCode:121.买卖股票的最佳时机1
  • C语言教程——文件处理(2)
  • OpenCV:图像轮廓
  • Rust 控制流语法详解
  • C++11新特性之范围for循环
  • 可被electron等调用的Qt截图-录屏工具【源码开放】
  • 在AWS上使用Flume搜集分布在不同EC2实例上的应用程序日志具体流程和代码
  • Golang 并发机制-4:用Mutex管理共享资源
  • 毕业设计:基于卷积神经网络的鲜花花卉种类检测算法研究
  • 51单片机 02 独立按键
  • 享元模式——C++实现
  • Java基础知识总结(四十)--Java.util.Properties
  • 浅析服务器虚拟化技术
  • unity学习26:用Input接口去监测: 鼠标,键盘,虚拟轴,虚拟按键
  • Leetcode:598
  • 深入核心:一步步手撕Tomcat搭建自己的Web服务器
  • Ubuntu 下 nginx-1.24.0 源码分析 ngx_debug_init();
  • 构建一个文档助手Agent:提升知识管理效率的实践
  • CUDA内存模型
  • 力扣经典题目之3无重复字符的最长子串
  • STL之初识string
  • 浅谈 JSON 对象和 FormData 相互转换,打通前端与后端的通信血脉_json转formdata
  • Baklib推动内容中台与人工智能技术的智能化升级与行业变革
  • Qt 5.14.2 学习记录 —— 이십삼 绘图API
  • MATLAB基础应用精讲-【数模应用】梯度直方图(HOG)(附C++和python代码实现)(二)
  • 攻防世界 php2