【探讨C++内存管理:有效避免内存泄漏与提高性能的关键】(上)

简介: 【探讨C++内存管理:有效避免内存泄漏与提高性能的关键】

【本节目标】


  • 1. C/C++内存分布
  • 2. C语言中动态内存管理方式
  • 3. C++中动态内存管理
  • 4. operator new与operator delete函数
  • 5. new和delete的实现原理
  • 6. 定位new表达式(placement-new)
  • 7. 常见面试题


1. C/C++内存分布


我们先来看一下内存分布图


【说明】:内存划分的意义:不同的数据,有不同的存储需求,各个区域满足不同的需求。

  • 1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  • 2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  • 3. 堆用于程序运行时动态内存分配,堆是可以上增长的,应用较多的场景:数据结构
  • 4. 数据段--存储全局数据静态数据
  • 5. 代码段--可执行的代码(经过链接形成的二进制文件)/只读常量,该区域不可被修改。我们平时在vs上写的代码是存在磁盘上的。

  • 强制修改也不行


我们先来看下面的一段代码和相关问题。

#include <iostream>
using namespace std;
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. 作用域:
  • 静态变量的作用域限定在声明它的函数内部,即它只在包含它的函数中可见。
  • 全局变量的作用域是整个文件,可以在文件中的任何地方访问。
  1. 生命周期:
  • 静态变量在程序运行期间一直存在,不会因为函数的调用结束而被销毁。它在第一次进入声明它的函数时初始化,然后在程序生命周期内保持其值。
  • 全局变量也在程序运行期间一直存在,它在程序启动时初始化,直到程序结束。


2. C语言中动态内存管理方式:malloc/calloc/realloc/free


void Test()
{
  int* p1 = (int*)malloc(sizeof(int));
  free(p1);
  // 1.malloc/calloc/realloc的区别是什么?
  int* p2 = (int*)calloc(4, sizeof(int));
  int* p3 = (int*)realloc(p2, sizeof(int) * 10);
  // 这里需要free(p2)吗?
  free(p3);
}


  1. malloc(Memory Allocation):


  • 函数原型:void* malloc(size_t size);
  • 用途:用于分配指定大小的内存块,返回一个指向该内存块起始地址的指针。
  • 行为:分配的内存块中的初始内容是未定义的,可能包含任意值。需要注意,malloc 不会初始化分配的内存。


  1. calloc(Contiguous Allocation):


  • 函数原型:void* calloc(size_t num_elements, size_t element_size);
  • 用途:用于分配一块指定数量和大小的连续内存块,返回一个指向该内存块起始地址的指针。
  • 行为:分配的内存块中的每个字节都会被初始化为零。相比于 malloc,calloc 提供了初始化内存的功能,适用于需要确保分配内存中的所有位都为零的情况。


  1. realloc(Reallocate):


  • 函数原型:void* realloc(void* ptr, size_t size);
  • 用途:用于更改之前分配的内存块的大小,可以扩大或缩小。
  • 行为:realloc 可以通过改变原有内存块的大小来满足新的需求。如果扩大内存块,新分配的内存区域的内容是未定义的,而原来的部分会保持不变。如果缩小内存块,多余的部分将会被释放。如果 ptr 是 NULL,realloc 的行为就相当于 malloc(size)。


malloc的实现原理?链接


3. C++内存管理方式


C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。


3.1 new/delete操作内置类型


void Test()
{
  // 动态申请一个int类型的空间
  int* ptr1 = new int;
  // 动态申请一个int类型的空间并初始化为10
  int* ptr2 = new int(10);
  // 动态申请3个int类型的空间
  int* ptr3 = new int[3];
  // 动态申请3个int类型的空间并初始化为1,2,3
  int* ptr4 = new int[3] {1, 2, 3};
  // 动态申请5个int类型的空间并初始化为1,2,3
  int* ptr5 = new int[5] {1, 2, 3};
  delete ptr1;
  delete ptr2;
  delete[] ptr3;
  delete[] ptr4;
  delete[] ptr5;
}


运行结果:



注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用 new[]和delete[],注意:匹配起来使用。


3.2 new和delete操作自定义类型


class A
{
public:
  A(int a = 0)
    : _a(a)
  {
    cout << "A():" << this << endl;
  }
  ~A()
  {
    cout << "~A():" << this << endl;
  }
private:
  int _a;
};
int main()
{
  // malloc不方便解决动态申请的自定义类型对象的初始化问题
  // new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间\
  还会调用构造函数和析构函数
  // new的本质:开空间+调用构造函数初始化
  // delete的本质:调用析构函数+释放空间
  A* p1 = (A*)malloc(sizeof(A));
  //p1->~A(1);//error C2521: 析构函数 不带任何参数
  A* p2 = new A(1); 
  free(p1);
  delete p2;                    
  // 内置类型是几乎是一样的
  int* p3 = (int*)malloc(sizeof(int)); // C
  int* p4 = new int;
  free(p3);
  delete p4;
  // 多个对象
  A* p5 = (A*)malloc(sizeof(A) * 10);
  free(p5); 
  A aa1(1);
  A aa2(2);
  A aa3(3);
  A* p6 = new A[10]{ aa1, aa2, aa3 };//有名对象
  A* p7 = new A[10]{ A(1),A(2),A(3) };//匿名对象
  A* p8 = new A[10]{ 1 , 2, 3 };//隐式类型转换
  delete[] p6;
  delete[] p7;
  delete[] p8;
  return 0;
}


注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与 free不会。同时我们这里还要注意,malloc开辟失败会返回空指针,而new开辟失败会抛异常。


【探讨C++内存管理:有效避免内存泄漏与提高性能的关键】(下):https://developer.aliyun.com/article/1425600

相关文章
|
7天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
25 6
|
6天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
22 4
|
7天前
|
监控 安全 程序员
如何使用内存池池来优化应用程序性能
如何使用内存池池来优化应用程序性能
|
10天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
16天前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
113 9
|
25天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
29天前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
36 0
【C++打怪之路Lv6】-- 内存管理
|
20天前
|
存储 分布式计算 安全
阿里云服务器内存型r7、内存型r8y、内存型r8i实例规格性能对比与选择参考
在选择阿里云服务器实例规格时,针对内存密集型应用和数据库应用,内存型r7、内存型r8y和内存型r8i实例是这部分应用场景选择最多的热门实例规格。为了帮助大家更好地了解这三款实例的区别,并为选择提供参考,本文将详细对比它们的实例规格、CPU、内存、计算、存储、网络等方面的性能,并附上活动价格对比。让大家了解一下他们之间的不同,以供参考选择。
|
5天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
28 4
|
29天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
26 4