C++: 内存管理 (new / delete)

文章目录

  • 一. C/C++ 内存分布
  • 二. C 语言中动态内存管理方式: malloc/calloc/realloc/free
  • 三. C++内存管理方式
    • 1. new / delete 操作内置类型
    • 2. new / delete 操作自定义类型
  • 四. operator new 与 operator delete 函数
  • 五. new 和 delete 的实现原理
    • 1. 内置类型
    • 2. 自定义类型
  • 六. 定位 new 表达式 (placement-new)
  • 七. malloc/free 和 new/delete 的区别

一. C/C++ 内存分布

在这里插入图片描述
在这里插入图片描述

下面来看一段代码和相关内存分布问题

#define _CRT_SECURE_NO_WARNINGS   1
#include<iostream>
#include<string.h>
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";
	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);

	std::cout << "sizeof(num1):" <<sizeof(num1) << std::endl;
	std::cout << "sizeof(char2):"<<sizeof(char2) << std::endl;
	std::cout << "strlen(char2):" << strlen(char2) << std::endl;
	std::cout << "sizeof(pChar3):" << sizeof(pChar3) << std::endl;
	std::cout << "strlen(pChar4):" << strlen(pChar3) << std::endl;
	std::cout << "sizeof(ptr1):" << sizeof(ptr1) << std::endl;
}

int main()
{
	Test();
	return 0;
}

在这里插入图片描述

  • globaVar 是全局变量, 所有文件都可以访问, 存放在数据段.
  • staticGlobalVar 是静态全局变量, 只有当前文件可以访问, 存放在数据段.
  • staticVar 是静态局部变量, 只有当前函数可以访问, 存放在数据段.
  • localVar 是局部变量, 存放在栈.
  • num1 是局部变量, 存放在栈.

在这里插入图片描述

  • char2 是数组名, 字符串"abcd"存放在常量区, 在创建数组的时候, 会拷贝一份 "abcd\0" 在栈空间, 首元素'a'的地址就是数组名的地址.在栈空间.
  • *char2 是对数组首元素地址的解引用, *char2 相当于数组首元素 a , 在栈空间.
  • pChar3 是一个指针变量, 指向了存放在数据区的常量字符串 "abcd", const 限定的是 *pChar3, 存放在栈.
  • *pChar3 是对pChar3 的解引用, 实际上就是数据区中的 a, 在数据段
  • ptr1 指向了是通过 malloc 后返回的空间的首地址, 但是 ptr1 本身是 main 函数临时创建的, 存放在栈空间
  • *ptr1 是动态开辟空间的第一块四字节大小的空间的数据, 存放在堆空间

在这里插入图片描述

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

void Test()
{
  int* p1 = (int*)malloc(sizeof(int));
  free(p1);

  int* p2 = (int*)calloc(4, sizeof(int));
  int* p3 = (int*)realloc(p2, sizeof(int) * 10);

  //free(p2);  // 这里需要free(p2)吗?
  free(p3);
}

这里 p2 不需要自己手动释放, realloc 如果申请空间成功, 会自动释放原来的空间.
在这里插入图片描述

  • malloccalloc 的唯一区别就是 calloc 会将申请空间初始化为 0, 而 malloc 则不会对申请到的空间进行初始化处理.
  • realloc: 如果原地址可以存放新开辟的空间, 则直接原地址进行扩容; 如果原地址没有那么大的空间, 则会新找到一块空间, 同时将原来的空间自动释放, 返回新空间的起始地址.

三. C++内存管理方式

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

1. new / delete 操作内置类型

void Test()
{
  // 动态申请一个 int 类型的空间, 不会初始化
  int* ptr1 = new int;

  // 动态申请一个 int 类型的空间, 并初始化为 10
  int* ptr2 = new int(10);

  // 动态申请十个 int 类型的空间, 不会初始化
  int* ptr3 = new int[10];

  // 动态申请十个 int 类型的空间, 并按照列表初始化
  int* ptr4 = new int[10]{1, 2, 3};       //第四个元素开始被默认初始化为 0

  delete(ptr1);
  delete(ptr2);
  delete[](ptr3);
  delete[](ptr4);
}

注意:
申请和释放单个元素的空间, 使用 newdelete 操作符.
申请和释放连续的空间, 使用 new[]delete[] 操作符.
newmalloc 在用法上没有区别, 区别就是 new 可以在申请空间的时候同时对那一块空间进行初始化, 而 malloc 只可以申请空间.

2. new / delete 操作自定义类型

class A
{
  public:
    A(int a)
      :_a(a)
    {
      cout << "A(int a): " << this << endl;
    }

    ~A()
    {
      cout << "~A(): " << this << endl;
    }

  private:
    int _a;
};

int main()
{
  // new/delete 和 malloc/free 的最大区别就是 new/delete 对于自定义类型出了开空间和释放空间还会调用构造函数和析构函数
  A *ptr1 = (A*)malloc(sizeof(A));
  A *ptr2 = new A(1);
  free(ptr1);
  delete(ptr2);

  return 0;
}

结果如下:
在这里插入图片描述

newdelete 会调用自定义类型的构造函数和析构函数.
mallocfree 不会, 它们只会申请空间和释放空间.


new 一块连续的空间, 有几个元素就要调用几次构造函数和析构函数, 同时会按照先构造后析构的顺序进行.

// new[] / delete[]
  A* ptr3 = new A[3]{1, 2, 3};  // C++11 使用列表初始化
  delete[](ptr3);

在这里插入图片描述


下面有一个更为复杂一点的栈自定义类型

class Stack
{
  public:
    Stack(int capacity = 4)
    :_capacity(capacity)
    ,_top(0)
    {
      cout << "Stack(int capacity = 4)" << endl;
      _array = new int[_capacity];
    }

    ~Stack()
    {
      cout << "~Stack()" << endl;
      delete[](_array);
      _array = nullptr;
      _capacity = _top = 0;
    }

  private:
    int* _array;
    int _capacity;
    int _top;
};

int main()
{
  Stack* p1 = new Stack(5);
  delete(p1);

  return 0;
}

在这里插入图片描述

在这里插入图片描述

对于自定义类型, new/deletemalloc/free 的区别就是 new/delete 会分别调用构造函数和析构函数.

在这里, 直接 free(p1) 肯定是有问题的, free 在释放自定义类型对象空间前不会调用该自定义类型的析构函数, 那么 _array 指向的空间没有被释放, 造成了内存泄漏.


对于 new 操作, 如果 new 失败怎么处理呢?
在 C++ 中是使用捕捉异常来处理的, 之后会详细讲解, 这里简单展示以下用法.

void Test()
{
  char* p1 = new char[1024*1024*1024];
  cout << (void*)p1 << endl;              // char*类型不能直接使用cout, 会被自动识别成字符串

  char* p2 = new char[1024*1024*1024];
  cout << (void*)p2 << endl;
}

int main()
{
  try
  {
    Test();
  }
  catch(const std::exception& e)
  {
    cout << "1" << endl;
    std::cerr << e.what() << '\n';
  }

  return 0;
}

第二次开辟空间失败, 会直接捕捉到异常并打印出异常, 程序并不会崩.
在这里插入图片描述

四. operator new 与 operator delete 函数

newdelete 是用户进行 动态内存申请和释放的操作符, operator newoperator delete 是系统提供的全局函数.
new 在底层通过调用 operator new 全局函数来申请空间, delete 在底层通过调用 operator delete 全局函数来释放空间.

operator newoperator delete 函数就是库全局函数, 是 mallocfree 的封装, 不调用构造函数和析构函数.

如果申请空间失败则会直接抛异常, 而原来的 malloc 只能返回 0.

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) __THROW1(__STD bad_alloc)
{
  // try to allocate size bytes
  void *p;
  while ((p = malloc(size)) == 0)
    if (_callnewh(size) == 0)
    {
      // report no memory
      // 如果内存申请失败了, 这里会抛出bad_alloc 类型异常
      static const std::bad_alloc nomem;
      _RAISE(nomem);
    }

  return (p);
}

/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{
     _CrtMemBlockHeader * pHead;
     RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
     if (pUserData == NULL)
         return;
     _mlock(_HEAP_LOCK);  /* block other threads */
     __TRY
         /* get a pointer to memory block header */
         pHead = pHdr(pUserData);
          /* verify block type */
         _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
         _free_dbg( pUserData, pHead->nBlockUse );
     __FINALLY
         _munlock(_HEAP_LOCK);  /* release other threads */
     __END_TRY_FINALLY
     return;
}
/*
free的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

以下三种对于内置类型的动态开辟内存空间的操作是一样的.

int main()
{
  int* p1 = (int*)malloc(sizeof(int));
  free(p1);

  int* p2 = new int;
  delete(p2);

  int* p3 = (int*)operator new(sizeof(int));
  operator delete(p3);

  return 0;
}

在这里插入图片描述

在这里插入图片描述


那么下面的代码也能知道底层是怎么调用的了.

Stact* p1 = new Stack[10];
delete[](p1);

首先调用 operator new 开辟了 160 字节大小的空间, 随后调用了 10 次构造函数, 创造了 10 个 Stack 类的自定义类型对象.

但是通过调试却发现, 开辟了不是 160 字节, 而是 168 字节, 传入 operator new 的参数 sz168

在这里插入图片描述

开辟完空间后, p1 的值为 0x614c28 , 通过观察 0x614c20, 发现多开辟的 8 字节空间, 存放了数组元素个数 10

在这里插入图片描述

更换代码, Stack* p1 = new Stack[8];
发现 p1 指向地址的向前 8 个字节确实为 8

在这里插入图片描述

这个数字存在是因为自定义类型有析构函数, 需要知道要调用多少次析构函数.

这个时候, 如果我错误的使用了 delete 而非 delete[], 会直接导致程序崩溃, 程序真实是在 0x614c20 开始申请了 168 个字节大小的空间. 使用 delete[] 会自动把该地址传入 operator delete, 而 delete 不会这么处理, 会把原来的 0x614c28 传入函数, 这个地址不是申请空间的首地址, 释放失败.

在这里插入图片描述

但如果是内置类型, 则有可能不会崩溃

int* p2 = new int[10];
delete(p2);

内置类型没有析构函数, 也就不需要多开一块空间用来记录需要调用多少次析构函数.这样, 程序也就不会崩溃.

在这里插入图片描述


通过上面的调试, 可以得到下面的编程注意.

new/delete, new[]/delete[], malloc/free一定要配套使用, 否则结果是未确定的.

五. new 和 delete 的实现原理

1. 内置类型

如果申请的是内置类型的空间, newmalloc, deletefree 基本相似, 不同的地方是:
new/delete 申请和释放的是单个元素的空间, new[]/delete[] 申请和释放的是连续空间.
new 申请失败时会抛异常, malloc 则会返回 NULL

2. 自定义类型

  • new 的原理

    1. 调用 operator new 函数构造空间
    2. 在申请的空间上执行构造函数, 完成对象的构造
  • delete 的原理

    1. 在空间上执行析构函数, 完成对象中资源的清理工作
    2. 调用 operator delete 函数释放对象的空间
  • new[] 的原理

    1. 调用 operator new[] 函数, 在 operator new[] 中实际调用 operator new 函数完成 N 个对象空间的申请
    2. 在申请的空间上执行 N 次构造函数
  • delete[] 的原理

    1. 在释放的对象空间上执行 N 次析构函数, 完成 N 个对象中资源的清理
    2. 调用 operator delete[] 释放空间, 实际在 operator delete[] 中调用 operator delete 来释放空间

六. 定位 new 表达式 (placement-new)

定位 new 表达式是在已分配的原始内存空间中调用构造函数初始化一个对象

使用格式:

new(place_address)type 或者 new(place_address)type(initializer-list)

place_address 必须是一个指针, initializer-list 是类型的初始化列表.

使用场景:
定位 new 表达式在实际中一般是配合内存池使用. 因为内存池分配出的内存没有初始化, 所以如果是自定义类型的对象, 就需要使用 new 的定义表达式进行显示调用构造函数进行初始化.

class A
{
  public:
    A(int a = 0)
      :_a(a)
    {
      cout << "A():" << this << endl;
    }

    ~A()
    {
      cout << "~A(): " << this << endl;
    }

  private:
    int _a;
};

int main()
{
  A* p1 = (A*)malloc(sizeof(A));          // 此时p1所指向的空间并没有被初始化, malloc只有申请空间的作用
  new(p1)A;   // 使用定位new进行p1对象的构造
  p1->~A();   // 可以显示调用析构函数
  free(p1);

  A* p2 = (A*)operator new(sizeof(A));    // operator new 同样只申请空间 并不初始化空间
  new(p2)A(2);  // 使用列表进行初始化
  p2->~A();
  operator delete(p2);

  return 0;
}

最后确实使用定位 new 完成了对未初始化空间的初始化构造

在这里插入图片描述

七. malloc/free 和 new/delete 的区别

malloc/freenew/delete 的共同点是: 都是从堆上申请空间, 并且需要用户手动释放.
不同的地方是:

  1. mallocfree 是函数, newdelete 是操作符.
  2. malloc 申请的空间不会初始化, new 可以初始化.
  3. malloc 申请空间时, 需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可, 如果是多个对象, [] 中指定对象个数即可.
  4. malloc 的返回值为 void*, 在使用时必须强转, new 不需要, 因为 new 跟的是空间的类型.
  5. malloc 申请空间失败时, 返回的是 NULL, 因此使用时必须判空, new 不需要, 但是 new 需要捕获异常.
  6. 申请自定义类型对象时, malloc/free 只会开辟空间, 不会调用构造函数和析构函数, 而 new 在申请空间后会调用构造函数完成对象的初始化, delete 在释放空间前会调用析构函数完成空间中资源的清理.

本章完.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/137227.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【 第八章】软件设计师 之 计算机软件法律法规

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 备考资料导航 软考好处&#xff1a;软考的…

程序员的护城河:职业发展的关键元素

目录 1. 技术深度与广度 2. 项目经验与实际操作 3. 沟通与团队协作 4. 持续学习与自我更新 5. 社区参与与开源贡献 6. 创新思维与解决问题的能力 7. 职业规划与自我管理 结语 在科技日新月异的今天&#xff0c;程序员的竞争已经不再仅仅依赖于技术水平&#xff0c;而是…

路径总和[简单]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你二叉树的根节点root和一个表示目标和的整数targetSum。判断该树中是否存在 根节点到叶子节点的路径&#xff0c;这条路径上所有节点值相加等于目标和targetSum。如果存在&#xff0c;返回true&#xff1b;否则&#xff0c;返回fa…

基于SpringMVC模式的电器网上订购系统的设计

大家好我是玥沐春风&#xff0c;今天分享一个基于SpringMVC模式的电器网上订购系统的设计&#xff0c;项目源码以及部署相关请联系我&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 本系统利用现在比较广泛的JSP结合后台SpringMybatisAjax编写程序的方式实现的。 在…

【C++入门】构造函数析构函数

目录 前言 1. 类的默认成员函数 2. 构造函数 2.1 什么是构造函数 2.2 构造函数的特性 3. 析构函数 3.1 什么是析构函数 3.2 析构函数的特性 前言 前边我们已经了解了类和对像的基本概念&#xff0c;今天我们将继续深入了解类。类有6个默认成员函数&#xff0c;即使类中什么都…

Golang 字符串处理汇总

1. 统计字符串长度&#xff1a;len(str) len(str) 函数用于统计字符串的长度&#xff0c;按字节进行统计&#xff0c;且该函数属于内置函数也不用导包&#xff0c;直接用就行&#xff0c;示例如下&#xff1a; //统计字符串的长度,按字节进行统计: str : "golang你好&qu…

【数据库开发】DataX开发环境的安装部署(Python、Java)

文章目录 1、简介1.1 DataX简介1.2 DataX功能1.3 支持的数据通道 2、DataX安装配置2.1 DataX2.2 Java2.3 Python 3、DataX Web安装配置3.1 mysql3.2 DataX Web3.2.1 简介3.2.2 架构图3.2.3 依赖环境3.2.4 安装 4、入门使用4.1 DataX自带打印示例测试4.2 DataX生成任务模板文件4…

Leetcode—234.回文链表【简单】

2023每日刷题&#xff08;二十七&#xff09; Leetcode—234.回文链表 直接法实现代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ bool isPalindrome(struct ListNode* head) {if(head NULL) {return t…

ablation study

文章目录 ablation study1、消融实验思想是什么&#xff1f;2、消融实验意义3、消融实验应用场景举例 ablation study 1、消融实验思想是什么&#xff1f; “消融实验”&#xff08;ablation study&#xff09;通常指的是通过逐步移除系统的一部分来评估该系统的贡献。这种方法…

相机突然断电,保存的DAT视频文件如何打开

3-6 本文主要解决因相机突然断电导致拍摄的视频文件打不开的问题。 在平常使用相机拍摄视频&#xff0c;比如使用佳能相机拍摄视频的时候&#xff0c;如果电池突然断电&#xff0c;就非常有可能会导致视频没来得及保存而损坏的情况&#xff0c;比如会产生下图中的这种DAT文件…

【Bug】当用opencv库的imread()函数读取图像,用matplotlib库的plt.imshow()函数显示图像时,图像色彩出现偏差问题的解决方法

一&#xff0c;问题描述 我们在利用opencv的imread读取本地图像&#xff0c;进行一系列处理&#xff0c;但是发现用matplotlib库的imshow&#xff08;&#xff09;函数显示的时候出现色彩改变&#xff0c;比如图像偏黄&#xff0c;偏红&#xff0c;偏蓝等等&#xff0c;但是对…

lesson05-C++模板

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 泛型编程 函数模板 类模板 泛型编程 我们先看一个代码&#xff1a; 看着是不是有点麻烦&#xff0c;我们有没有一种通用的办法&#xff0c;让编译器能够根据不同的类型自动生成不同的函数呢&#xff1f;有&#xff…

【JUC】二、线程间的通信(虚假唤醒)

文章目录 0、多线程编程的步骤1、wait和notify2、synchronized下实现线程的通信&#xff08;唤醒&#xff09;3、虚假唤醒4、Lock下实现线程的通信&#xff08;唤醒&#xff09;5、线程间的定制化通信 0、多线程编程的步骤 步骤一&#xff1a;创建&#xff08;将来被共享的&am…

c primer plus_chapter_four——字符串和格式化输入/输出

1、strlen&#xff08;&#xff09;&#xff1b;const&#xff1b;字符串&#xff1b;用c预处理指令#define和ANSIC的const修饰符创建符号常量&#xff1b; 2、c语言没有专门储存字符串的变量类型&#xff0c;字符串被储存在char类型的数组中&#xff1b;\0标记字符串的结束&a…

低价寄快递寄件微信小程序 实际商用版 寄快递 低价寄快递小程序(源代码+截图)前后台源码

盈利模式 快递代下CPS就是用户通过线上的渠道&#xff08;快递小程序&#xff09;&#xff0c;线上下单寄快递来赚取差价&#xff0c;例如你的成本价是5元&#xff0c;你在后台比例设置里面设置 首重利润是1元&#xff0c;续重0.5元&#xff0c;用户下1kg的单页面显示的就是6元…

LiteVNA 能做什么?

最近入手了一台 LiteVNA 设备&#xff0c;性价比非常高。因为之前没有接触过 VNA 这种测试仪器&#xff0c;所以准备好好研究一下。和它类似的一个项目是 NanoVNA6000&#xff0c;价格要高些&#xff0c;但可能性能要好点&#xff0c;另外&#xff0c;文档也要全一些。 VNA …

C++跨DLL内存所有权问题探幽(一)DLL提供的全局单例模式

最近在开发的时候&#xff0c;特别是遇到关于跨DLL申请对象、指针、内存等问题的时候遇到了这么一个问题。 问题 跨DLL能不能调用到DLL中提供的单例&#xff1f; 问题比较简单&#xff0c;就是我现在有一个进程A&#xff0c;有DLL B DLL C&#xff0c;这两个DLL都依赖DLL D的…

Linux系统编程——修改配置文件(应用)

该应用主要调用到strstr函数&#xff0c;我们只需调用该函数并传入相关文件和修改数值即可&#xff0c;下面就是对strstr函数的定义解读以及实现案例 1.调用strstr函数需要包含以下头文件 #include<string.h>2.函数定义格式 char *strstr(char *str1, const char *str…

深度学习4:BatchNormalization(批规范化)

一、起源 训练深度网络的时候经常发生训练困难的问题&#xff0c;因为&#xff0c;每一次参数迭代更新后&#xff0c;上一层网络的输出数据经过这一层网络计算后&#xff0c;数据的分布会发生变化&#xff0c;为下一层网络的学习带来困难。 Batch Normalizatoin 之前的解决方…

c语言:解决数组中数组缺少单个的元素的问题

题目&#xff1a;数组nums包含从0到n的所以整数&#xff0c;但其中缺了一个。请编写代码找出那个缺失的整数。O(n)时间内完成。 如&#xff0c;输入&#xff1a;【3&#xff0c;0&#xff0c;1】。 输出&#xff1a; 2 三种方法 &#xff1a; 方法1&#xff1a;排序&#xf…