【探讨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

相关文章
|
22天前
|
存储 缓存 算法
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
【C/C++ 性能优化】提高C++程序的缓存命中率以优化性能
111 0
|
2天前
|
存储 缓存 监控
Java内存管理:垃圾回收与内存泄漏
【4月更文挑战第16天】本文探讨了Java的内存管理机制,重点在于垃圾回收和内存泄漏。垃圾回收通过标记-清除过程回收无用对象,Java提供了多种GC类型,如Serial、Parallel、CMS和G1。内存泄漏导致内存无法释放,常见原因包括静态集合、监听器、内部类、未关闭资源和缓存。内存泄漏影响性能,可能导致应用崩溃。避免内存泄漏的策略包括代码审查、使用分析工具、合理设计和及时释放资源。理解这些原理对开发高性能Java应用至关重要。
|
22天前
|
安全 程序员 C++
【C++ 基本知识】现代C++内存管理:探究std::make_系列函数的力量
【C++ 基本知识】现代C++内存管理:探究std::make_系列函数的力量
97 0
|
23天前
|
存储 Linux 程序员
【Linux C/C++ 堆内存分布】深入理解Linux进程的堆空间管理
【Linux C/C++ 堆内存分布】深入理解Linux进程的堆空间管理
68 0
|
23天前
|
算法 Java C++
【C/C++ 内存知识扩展】内存不足的可能性分析
【C/C++ 内存知识扩展】内存不足的可能性分析
12 0
|
23天前
|
存储 算法 Linux
深入理解Linux内存管理brk 和 sbrk 与以及使用C++ list实现内存分配器
深入理解Linux内存管理brk 和 sbrk 与以及使用C++ list实现内存分配器
31 0
|
21天前
|
存储 C++ 容器
C++入门指南:string类文档详细解析(非常经典,建议收藏)
C++入门指南:string类文档详细解析(非常经典,建议收藏)
31 0
|
21天前
|
存储 编译器 C语言
C++入门: 类和对象笔记总结(上)
C++入门: 类和对象笔记总结(上)
30 0
存储 编译器 Linux
13 0
|
1天前
|
编译器 C++
标准库中的string类(上)——“C++”
标准库中的string类(上)——“C++”