【C++ 面试 - 内存管理】每日 3 题(八)

✍个人博客:Pandaconda-CSDN博客

📣专栏地址:http://t.csdnimg.cn/fYaBd

📚专栏简介:在这个专栏中,我将会分享 C++ 面试中常见的面试题给大家~

❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

22. delete p 和 delete[] p 的区别

在 C++ 中,deletedelete[] 都是用于释放通过 newnew[] 分配的内存,但它们的使用场景是不同的。

delete p 用于释放单个对象的内存。这意味着在使用 new 分配内存后,如果你只用一个指针指向这个对象,就应该使用 delete p 来释放内存。例如:

cpp 复制代码
int* p = new int;
// 使用 p 指针操作对象
delete p; // 释放内存

delete[] p 用于释放数组对象的内存。这意味着在使用 new[] 分配内存后,如果你用一个指针指向一个数组的起始地址,就应该使用 delete[] p 来释放内存。例如:

cpp 复制代码
int* p = new int[10];
// 使用 p 指针操作数组对象
delete[] p; // 释放内存

使用 delete 释放数组对象的内存或者使用 delete[] 释放单个对象的内存,会导致未定义的行为(Undefined Behavior),可能会导致内存泄漏或程序崩溃。

因此,当我们使用 newnew[] 分别分配单个对象和数组对象时,需要确保使用与其对应的 deletedelete[] 来正确释放内存,以避免内存泄漏和其他潜在问题。

23. new 和 delete 的实现原理,delete 是如何知道释放内存的大 小的?

1、 new 简单类型直接调用 operator new 分配内存;而对于复杂结构,先调用 operator new 分配内存,然后在分配的内存上调用构造函数。

对于简单类型,new[] 计算好大小后调用 operator new;

对于复杂数据结构,new[] 先调用 operator new[] 分配内存,然后在 p 的前四个字节写入数组大小 n,然后调用 n 次构造函数,针对复杂类型,new[] 会额外存储数组大小;

① new 表达式调用一个名为 operator new (operator new[]) 函数,分配一块足够大的、原始的、未命名的内存空间;

② 编译器运行相应的构造函数以构造这些对象,并为其传入初始值;

③ 对象被分配了空间并构造完成,返回一个指向该对象的指针。

2、 delete 简单数据类型默认只是调用 free 函数;复杂数据类型先调用析构函数再调用 operator delete。

针对简单类型,delete 和 delete[] 等同。假设指针 p 指向 new[] 分配的内存。因为要 4 字节存储数组大小,实际分配的内存地址为 [p-4],系统记录的也是这个地址。delete[] 实际释放的就是 p-4 指向的内存。而 delete 会直接释放 p 指向的内存,这个内存根本没有被系统记录,所以会崩溃。

3、 需要在 new[] 一个对象数组时,需要保存数组的维度,C++ 的做法是在分配数组空间时多分配了 4 个字节的大小,专门保存数组的大小,在 delete [] 时就可以取出这个保存的数,就知道了需要调用析构函数多少次了。

24. 什么是内存泄露,如何检测与避免?

(1)内存泄露

一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的 (内存块的大小可以在程序运行期决定) 内存块,使用完后必须显式释放的内存。应用程序般使用 malloc、realloc、new 等函数从堆中分配到块内存,使用完后,程序必须负责相应的调用 free 或 delete 释放该内存块。否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

(2)避免内存泄露的几种方式

  1. **使用智能指针:**智能指针是一种能够自动管理内存的对象,可以在不再需要对象时自动释放内存。C++ 标准库提供了智能指针类型,如 std::unique_ptr 和 std::shared_ptr,可以使用它们来管理动态分配的内存。

  2. 析构函数为虚函数: 一定要将基类的析构函数声明为虚函数

  3. 申请和释放操作成对出现: 有 new 就有 delete,有 malloc 就有 free,保证它们一定成对出现。对象数组的释放一定要用 delete[]

  4. **重载 new 和 delete:**手动管理内存时,应该重载 new 和 delete 运算符,以便在分配和释放内存时使用适当的函数。这可以避免使用不当的指针操作,从而减少内存泄漏的风险。

  5. **使用 RAII(资源获取即初始化)的思想:**RAII 是一种编程技术,它通过将资源的生命周期与对象的生命周期关联起来,确保资源在使用后被正确释放。在 C++ 中,可以使用类来封装动态分配的资源,并在类对象的生命周期结束时自动释放资源。

  6. **预先设定好内存池:**预先设定好内存池可以减少频繁的内存分配和释放操作,从而减少内存碎片和提高性能。在程序开始时,可以预先分配一定数量的内存块,并在需要时从内存池中分配内存。这样可以避免频繁地调用 new 和 delete 运算符,从而减少错误的可能性。

(3)检测方法

1. 计数法

使用 new 或者 malloc 时,让该数 +1,delete 或 free 时,该数 -1,程序执行完打印这个计数,如果不为 0 则表示存在内存泄露。

2. gcc 启用 asan 标志检查

GCC 中的 "启用 ASAN 标志检查" 是一种内存泄漏检测工具,它是 GCC 编译器的一个特性,称为 AddressSanitizer(ASan)。ASan 是一个动态分析工具,它可以检测程序中可能的内存泄漏和其他内存错误。

当你在编译程序时,添加 ASan 标志可以启用内存泄漏检测。当程序运行时,ASan 会跟踪分配和释放的内存,并检查是否存在未被释放的内存块。如果发现未被释放的内存块,ASan 会报告内存泄漏。

使用 ASan 进行内存泄漏检测的一般步骤如下:

  • 安装 GCC 编译器并启用 ASan 标志。

  • 编写代码,使用智能指针或 RAII 技术管理内存。

  • 编译程序时添加 ASan 标志。

  • 运行程序并检查任何内存泄漏报告。

3. 标记清除法(Mark-Sweep Garbage Collection)

标记清除法是一种内存泄漏检测方法,用于检测程序中可能存在的内存泄漏问题。标记清除法主要包括两个步骤:标记和清除。

在程序运行过程中,标记清除法会跟踪分配和释放的内存块,并使用标记来标识可能存在问题的内存区域。这些标记通常是由程序中的特定对象或数据结构来管理的。当程序运行到某个特定点时,标记清除法会遍历所有已分配的内存块,并检查每个内存块的状态。如果某个内存块已经被释放,但仍然被引用或使用,那么这个内存块就被认为是内存泄漏。

清除步骤则是将所有标记清除,以防止后续的引用或使用。在清除过程中,标记清除法会再次遍历所有已分配的内存块,并确保每个内存块的状态都被正确地清除。

标记清除法的优点在于它可以检测到内存泄漏问题,并且可以在程序运行时实时检测和报告问题。然而,它也存在一些缺点,例如可能会影响程序的性能和稳定性,尤其是在大规模程序中。此外,标记清除法需要正确地管理内存和跟踪分配和释放的操作,因此需要编写复杂的代码来正确地实现它。

4. Linux 下可以使用 Valgrind 工具

首先看一段 C 程序示例,比如:

cpp 复制代码
#include <stdlib.h>
int main()
{
    int *array = malloc(sizeof(int));
    return 0;
}

编译程序:gcc -g -o main main.c,比哪一需要加上 -g 选项打开调试,使用 IDE 的可以用 Debug 模式编译。

使用 Valgrind 检测内存使用情况:

html 复制代码
valgrind --tool=memcheck --leak-check=full ./main

结果:

html 复制代码
==31416== Memcheck, a memory error detector
==31416== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==31416== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==31416== Command: ./main_c
==31416==
==31416==
==31416== HEAP SUMMARY:
==31416==     in use at exit: 4 bytes in 1 blocks
==31416==   total heap usage: 1 allocs, 0 frees, 4 bytes allocated
==31416==
==31416== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==31416==    at 0x4C2DBF6: malloc (vg_replace_malloc.c:299)
==31416==    by 0x400537: main (main.c:5)
==31416==
==31416== LEAK SUMMARY:
==31416==    definitely lost: 4 bytes in 1 blocks
==31416==    indirectly lost: 0 bytes in 0 blocks
==31416==      possibly lost: 0 bytes in 0 blocks
==31416==    still reachable: 0 bytes in 0 blocks
==31416==         suppressed: 0 bytes in 0 blocks
==31416==
==31416== For counts of detected and suppressed errors, rerun with: -v
==31416== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

先看看输出信息中的 HEAP SUMMARY,它表示程序在堆上分配内存的情况,其中的 1 allocs 表示程序分配了 1 次内存,0 frees 表示程序释放了 0 次内存,4 bytes allocated 表示分配了 4 个字节的内存。

另外,Valgrind 也会报告程序是在哪个位置发生内存泄漏。例如,从下面的信息可以看到,程序发生了一次内存泄漏,位置是 main.c 文件的第 5 行:

html 复制代码
==31416== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==31416==    at 0x4C2DBF6: malloc (vg_replace_malloc.c:299)
==31416==    by 0x400537: main (main.c:5)

5. Win dows 下可以使用 CRT库

相关推荐
小白程序员成长日记6 分钟前
2025.12.01 力扣每日一题
算法·leetcode·职场和发展
爱装代码的小瓶子6 分钟前
【cpp知识铺子】map和set的前身-二叉搜索树
c++·算法
5***g29812 分钟前
Windows安装Rust环境(详细教程)
开发语言·windows·rust
q_191328469514 分钟前
基于Springboot2+Vue2的旅游景点购票系统
java·vue.js·spring boot·后端·mysql·毕业设计·计算机毕业设计
哈哈哈笑什么14 分钟前
基于RabbitMQ的企业级订单系统设计与实现
后端
LSTM9715 分钟前
使用 Java 实现条形码生成与识别
后端
哈哈哈笑什么15 分钟前
如何防止恶意伪造前端唯一请求id
前端·后端
哈哈哈笑什么15 分钟前
Spring Cloud 微服务架构下幂等性的 业务场景、解决的核心问题、完整实现方案及可运行代码
后端
XL's妃妃16 分钟前
Java 基准测试工具 JMH 详细介绍
java·开发语言·测试工具
PieroPC18 分钟前
飞牛Nas-通过Docker的Compose 安装WordPress
后端