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

相关文章
|
23天前
|
存储 缓存 编译器
【硬核】C++11并发:内存模型和原子类型
本文从C++11并发编程中的关键概念——内存模型与原子类型入手,结合详尽的代码示例,抽丝剥茧地介绍了如何实现无锁化并发的性能优化。
|
4天前
|
存储 程序员 编译器
什么是内存泄漏?C++中如何检测和解决?
大家好,我是V哥。内存泄露是编程中的常见问题,可能导致程序崩溃。特别是在金三银四跳槽季,面试官常问此问题。本文将探讨内存泄露的定义、危害、检测方法及解决策略,帮助你掌握这一关键知识点。通过学习如何正确管理内存、使用智能指针和RAII原则,避免内存泄露,提升代码健壮性。同时,了解常见的内存泄露场景,如忘记释放内存、异常处理不当等,确保在面试中不被秒杀。最后,预祝大家新的一年工作顺利,涨薪多多!关注威哥爱编程,一起成为更好的程序员。
|
1月前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
47 8
|
2月前
|
存储 缓存 C语言
【c++】动态内存管理
本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。
65 3
|
2月前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
62 6
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
194 4
|
3月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
2月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
488 1
|
1月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80