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

相关文章
|
11月前
|
消息中间件 存储 关系型数据库
千亿消息“过眼云烟”?Kafka把硬盘当内存用的性能魔法,全靠这一手!
Apache Kafka 是由 LinkedIn 开发并捐赠给 Apache 基金会的分布式消息队列系统,具备高吞吐、可扩展和容错能力。其核心设计围绕主题、分区、分段和偏移量展开,通过顺序写入磁盘和 Page Cache 提升性能,广泛应用于大数据实时处理场景。
451 0
|
存储 缓存 分布式计算
高内存场景必读!阿里云r7/r9i/r8y/r8i实例架构、性能、价格多维度对比
阿里云针对高性能需求场景,一般会在活动中推出内存型r7、内存型r9i、内存型r8y和内存型r8i这几款内存型实例规格的云服务器。相比于活动内的经济型e和通用算力型u1等实例规格,这些内存型实例在性能上更为强劲,尤其适合对内存和计算能力有较高要求的应用场景。这些实例规格的云服务器在处理器与内存的配比上大多为1:8,但它们在处理器架构、存储性能、网络能力以及安全特性等方面各有千秋,因此适用场景也各不相同。本文将为大家详细介绍内存型r7、r9i、r8y、r8i实例的性能、适用场景的区别以及选择参考。
|
弹性计算 安全 数据库
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
作为云计算服务核心组件,虚拟化内存管理直接影响业务系统性能表现。本文详解了内存优化方案与技术实践,助您降低30%资源浪费。
541 0
【转】云服务器虚拟化内存优化指南:提升性能的7个关键策略
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
1680 0
|
C语言 C++
c与c++的内存管理
再比如还有这样的分组: 这种分组是最正确的给出内存四个分区名字:栈区、堆区、全局区(俗话也叫静态变量区)、代码区(也叫代码段)(代码段又分很多种,比如常量区)当然也会看到别的定义如:两者都正确,记那个都选,我选择的是第一个。再比如还有这样的分组: 这种分组是最正确的答案分别是 C C C A A A A A D A B。
253 1
|
存储 缓存 数据挖掘
阿里云服务器实例选购指南:经济型、通用算力型、计算型、通用型、内存型性能与适用场景解析
当我们在通过阿里云的活动页面挑选云服务器时,相同配置的云服务器通常会有多种不同的实例供我们选择,并且它们之间的价格差异较为明显。这是因为不同实例规格所采用的处理器存在差异,其底层架构也各不相同,比如常见的X86计算架构和Arm计算架构。正因如此,不同实例的云服务器在性能表现以及适用场景方面都各有特点。为了帮助大家在众多实例中做出更合适的选择,本文将针对阿里云服务器的经济型、通用算力型、计算型、通用型和内存型实例,介绍它们的性能特性以及对应的使用场景,以供大家参考和选择。
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
624 12
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)