C++内存管理篇

文章目录

  • 1. C/C++内存分布
  • 2. C++中的内存管理方式
  • 3. operator new和operator delete函数
  • 4. new和delete的实现原理
  • 5. 定位new表达式(placement-new)

1. C/C++内存分布


C语言中,为了方便管理内存空间,将内存分成了不同的区域,每个区域管理不同的数据

C++中,保持着跟C语言一样的内存区域划分

在这里插入图片描述

int main() 
{int a = 0; const int b = 1; //很多人有个误区,认为被const修饰的变量在常量区 //实际上,被const修饰的变量叫做常变量,它本质还是个变量,只不过有了常属性 //因此b是在栈区上的 cout << &a << endl; cout << &b << endl; char arr1[] = "abcd"; const char* arr2 = "abcd"; arr1[0]++; //arr2[0]++; ((char*)arr2)[0]++; //即使将arr2强转成char*,程序在运行的过程中也会崩溃,暴力修改常量区的数据 return 0; 
}

2. C++中的内存管理方式


C++语言中,我们用malloc/calloc/realloc/free函数进行内存的开辟和释放,对内存进行管理

C++中,这些函数依然可用,但在使用上是比较麻烦的;因此C++中增加了new和delete操作符,使用这两个操作符进行内存的管理

  • C语言使用的是函数进行内存管理
  • C++中new和delete是操作符,不是函数
int main()
{//申请一个int类型的空间int* p1 = new int;//申请一个int类型的空间并初始化int* p2 = new int(1);//申请一个数组int* p3 = new int[10];//C++11支持//申请一个数据并初始化int* p4 = new int[10]{ 1,2,3 };delete p1;delete p2;delete[] p3;delete[] p4;return 0;
}

相比于C语言中利用函数管理内存,C++中的管理内存方式有哪些好处:

  1. 用法更加简洁

  2. 可以控制初始化

  3. 对于自定义类型,开辟空间+自动调用对应的构造函数

    struct ListNode
    {
    public:ListNode(int val):_val(val), _next(nullptr){}int _val;ListNode* _next;
    };int main()
    {ListNode* node1 = new ListNode(1);ListNode* node2 = new ListNode(2);ListNode* node3 = new ListNode(3);delete node1;delete node2;delete node3;return 0;
    }
    
  4. new失败了以后抛异常,不需要手动检查

    void Func2()
    {int n = 1;while (1){int* p = new int[1024 * 1024 * 10];cout << n << "->" << p << endl;n++;}
    }int main()
    {try{Func2();}catch (const exception& e){cout << e.what() << endl;}return 0;
    }
    
  5. 对于自定义类型,申请空间时,new会调用对应的构造函数;delete会调用对应的析构函数

    class A
    {
    public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}private:int _a;
    };
int main()
{A* p1 = new A(1);//调用一次构造delete p1;//调用一次析构A* p2 = new A[10];//调用十次构造delete[] p2;//调用十次析构return 0;
}

3. operator new和operator delete函数


看到operator+操作符,第一直觉会以为这是对new和delete操作符的重载函数,但其实它们不是重载函数

实际上,这两个函数是系统提供的全局函数

在底层,new通过调用operator new函数来实现申请空间;delete通过调用operator delete函数来时间空间的释放

operator new函数最终通过malloc函数申请空间,申请成功则返回,否则抛异常

operator delete函数最终通过free函数释放空间

可以看到,new和delete的最底层还是使用的malloc和free函数,只不过是对它进行了一系列封装

4. new和delete的实现原理


在这里插入图片描述

对于内置类型:

  • 两者的效果差不多,不同的是new在申请空间失败时抛异常,malloc则返回NULL

对于自定义类型:

  • new会调用operator new函数申请空间,再在申请的空间上调用构造函数
  • delete会调用析构函数完成对象中的资源清理,再调用operator delete释放对象的空间
  • new T[n]会调用operator new[]函数,operator new[]函数会调用operator new函数申请n个对象的空间
  • delete[]会调用n次析构函数,完成n个对象中资源的清理,再调用operator delete[]函数,operator delete[]安徽念书会调用operator delete函数,释放n个对象的空间
class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}private:int _a;
};int main()
{A* p3 = new A[10];delete[] p3;return 0;
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

当然,不是所有情况都会留出一块空间存放对象个数

内置类型不需要析构,也就不会留出空间

没有显示定义析构函数的自定义类型也不会留出空间,因为没有显示定义析构函数,就认为不需要析构函数释放对象中的资源

new和delete使用时一定要对应,否则可能会出现不确定的结果

int main()
{// 程序能运行,但不推荐int* p1 = new int(1);delete[] p1;//error 正确的写法:delete p1// 若A显示定义析构函数,程序陷入无限调用析构的死循环中// 若A没有显示定义析构函数,程序能运行,但不推荐A* p2 = new A(1);delete[] p2;//error 正确的写法:delete p2// 程序能运行,但不推荐int* p3 = new int[10];delete p3;//error 正确的写法:delete[] p3// 若A显示定义析构函数,程序报错,原因是释放的位置错了// 若A没有显示定义析构函数,程序能运行,但不推荐A* p4 = new A[10];delete p4;//error 正确的写法:delete[] p4return 0;
}
// C语言创建不带哨兵位的链表
typedef int DataType;struct ListNode
{DataType val;struct ListNode* next;
};struct ListNode* CreateNewNode(DataType val)
{struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));if (newNode == NULL){perror("malloc fail");exit(-1);}newNode->next = NULL;newNode->val = val;return newNode;
}struct ListNode* CreateList(int n)
{struct ListNode* head = CreateNewNode(-1);struct ListNode* tail = head;for (int i = 1; i <= n; i++){struct ListNode* temp = CreateNewNode(i);tail->next = temp;tail = tail->next;}struct ListNode* cur = head->next;free(head);return cur;
}int main()
{struct ListNode* head = CreateList(5);return 0;
}struct ListNode
{
public:ListNode(int val):_val(val),_next(nullptr){}int _val;ListNode* _next;
};// C++创建不带哨兵位的链表
ListNode* CreateList(int n)
{ListNode head(-1);ListNode* tail = &head;int val = 0;printf("请依次输入值:>");for (int i = 1; i <= n; i++){cin >> val;ListNode* newNode = new ListNode(val);tail->_next = newNode;tail = tail->_next;}return head._next;
}int main()
{ListNode* list = CreateList(5);return 0;
}

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


在某些场景下,我们向内存申请的空间没有初始化,比如向内存池申请的空间,如果是自定义类型的对象,我们可以使用new的定义表达式进行显示调用构造函数进行初始化

class A
{
public:A(int a = 0):_a(a){cout << "A(int a)" << endl;}~A(){cout << "~A()" << endl;}private:int _a;
};int main()
{A* p1 = (A*)operator new(sizeof(A));new(p1)A(1);p1->~A();operator delete(p1);return 0;
}

malloc/free和new/delete的区别:

  • 共同点:都是从堆上申请空间,需要自己手动释放空间

  • 不同点:

    用法上:

    1. malloc/free是函数;new/delete是操作符
    2. malloc申请的空间不会初始化;new会初始化
    3. malloc申请空间时,需要手动计算空间大小;new只需加上空间类型即可,想创建多个对象,只需在[]里加上对象的个数
    4. malloc的返回值为void*,需要做强转处理;new的返回值就是空间类型,不需要处理
    5. malloc申请空间失败时返回NULL,因此使用时必须先判空;new不需要,但new需要捕获异常

    底层特性上:

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算布尔二叉树的值

题目 题目链接 . - 力扣&#xff08;LeetCode&#xff09; 题目描述 代码实现 class Solution { public:bool evaluateTree(TreeNode* root) {if(root->left nullptr && root->right nullptr) return root->val;bool left evaluateTree(root->left)…

YOLOv8.1.0安装

【YOLO】YOLOv8训练环境配置 python 3.8.18 cuda 11.3.1 cudnn 8.2.1 pytorch 1.12.1-gpu版 - 知乎 (zhihu.com) 一、Anaconda 默认装好了可用的Anaconda&#xff0c;安装教程见Win10系统anaconda安装 - 知乎 (zhihu.com) 二、在虚拟环境下用conda安装 1.创建虚拟环境 …

STM32H750片外QSPI启动配置简要

STM32H750片外QSPI启动配置简要 &#x1f4cd;参考信息源&#xff1a;《STM32H750片外Flash启动(W25Q64JVSIQ)》&#x1f516;本例程基于Keil MDk开发平台。&#x1f341;配置框架&#xff1a; ✨为什么使用要使用QSPI启动方式 不管对于STM32H7系列单片机&#xff0c;还是其他…

【Java】快速排序

文章目录 一、什么是快速排序二、基准元素的选择1、选择第一个元素2、随机选择 三、元素的交换1、双边循环法2、单边循环法 一、什么是快速排序 快速排序是由冒泡排序演变而来&#xff0c;比冒泡排序更快的排序算法。之所以快&#xff0c;是因为快速排序用了分治法。 相同的是…

探索手指套的多功能用途

什么是手指套&#xff1f; 手指套&#xff0c;戴在手指上的用品。作为一种小巧实用的用品&#xff0c;在我们的生活中扮演着多种角色。无论是在工业生产中的保护&#xff0c;医疗操作中的防护&#xff0c;还是日常生活中的装饰&#xff0c;甚至是性生活中的辅助&#xff0c;手…

数据结构从入门到精通——队列

队列 前言一、队列1.1队列的概念及结构1.2队列的实现1.3队列的实现1.4扩展 二、队列面试题三、队列的具体实现代码Queue.hQueue.ctest.c队列的初始化队列的销毁入队列出队列返回队头元素返回队尾元素检测队列是否为空检测元素个数 前言 队列是一种特殊的线性数据结构&#xff…

Rust教程:How to Rust-从开始之前到Hello World

本文为第0篇 专栏简介 本专栏是优质Rust技术专栏&#xff0c;推荐精通一门技术栈的蟹友&#xff0c;不建议基础的同学&#xff08;无基础学Rust也是牛人[手动捂脸]&#xff09; 感谢Rust圣经开源社区的同学&#xff0c;为后来者提供了非常优秀的Rust学习资源 本文使用&…

银河麒麟V10 安装部署大数据管理软件 DataSophon

一、概览 1、愿景 致力于快速实现部署、管理、监控以及自动化运维大数据云原生平台&#xff0c;帮助您快速构建起稳定、高效、可弹性伸缩的大数据云原生平台。 2、DataSophon是什么 《三体》&#xff0c;这部获世界科幻文学最高奖项雨果奖的作品以惊艳的"硬科幻"…

ARM/Linux嵌入式面经(二):芯片原厂

uart如何进行通信&#xff0c;模块发给uart数据信息后经历了什么 UART&#xff08;Universal Asynchronous Receiver/Transmitter&#xff0c;通用异步收发传输器&#xff09;是一种用于串行通信的协议&#xff0c;它使用一对传输线&#xff08;TX和RX&#xff09;进行双向通信…

结构体内存对齐详解

目录 结构体对齐&#xff1a; 为什么要进行内存对齐&#xff1f; 关于结构体的详解文章&#xff1a;C语言结构体详解_结构体变量和结构体类型举例-CSDN博客 结构体对齐&#xff1a; 存储的时候和当前存储的成员类型字节大小和默认对齐数比较&#xff0c;取小值 存在该对齐数的…

大语言模型的“大”体现在哪里

大语言模型中的"大"通常体现在以下几个方面&#xff0c;参数数量&#xff0c;训练数据和计算资源&#xff1a; 参数数量&#xff1a; 大语言模型的一个显著特征是其庞大的参数数量。参数的数量决定了模型的复杂度和表示能力。更多的参数通常意味着模型可以捕捉更复…

【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点

文章目录 题目思路解答 题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[]示例 3&…