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

✍个人博客:Pandaconda-CSDN博客

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

📚专栏简介:在这个专栏中,我将会分享 C++ 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

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

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

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

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

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

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 程序示例,比如:

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

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

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

valgrind --tool=memcheck --leak-check=full ./main

结果:

==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 行:

==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库

  • 30
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值