C++初阶:内存管理

目录

  • 1. C/C++中各种资源的内存分布
    • 1.1 C/C++程序内存区域划分
    • 1.2 各资源的内存分布情况(练习)
  • 2. C++中的动态内存管理方式
    • 2.1 new/delete开辟内置类型空间
    • 2.2 new/delete开辟销毁自定义类型空间
  • 3. operator new 与 operator delete函数
  • 4. new与delete的实现原理
  • 5. 定位new表达式与池化计数
  • 6. malloc/free与new/delete的异同

1. C/C++中各种资源的内存分布

1.1 C/C++程序内存区域划分

正在执行的程序是在计算机的内存空间上运行的,C/C++为了程序的高效运行,将内存划分了多个区域来进行对不同特性种类资源的区别管理。

在这里插入图片描述

1.2 各资源的内存分布情况(练习)

//全局变量,数据段
int globalVar = 1;//静态全局变量,数据段
static int staticGlobalVar = 1;
void Test()
{//静态变量,数据段static int staticVar = 1;//局部变量,栈int localVar = 1;//局部变量,数组,栈//sizeof(num1),代表整个数组,40字节int num1[10] = { 1, 2, 3, 4 };//局部变量,字符数组,栈//sizeof(char2),代表整个数组,5字节//strlen(char2),字符串长度,4char char2[] = "abcd";//sizeof(pChar3),指针,4/8字节//strlen(pChar3),字符串长度,4//只读字符串,代码段const char* pChar3 = "abcd";//动态开辟空间,堆//ptr1指针,4/8字节int* ptr1 = (int*)malloc(sizeof(int) * 4);//calloc会用0进行申请空间的初始化int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);free(ptr1);free(ptr3);
}

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

  1. 在编写程序时,我们时常需要一段可以动态增长我们可以控制其开辟与销毁的空间,C语言中我们学习过在内存中动态开辟空间的方式,我们通过malloc与free来进行空间开辟与销毁。
  2. 而C++中有新的动态开辟空间的方法,new与delete,它们在使用上更加方便,且可以应用于更广泛的场景,接下来,就让我们来进行对其的学习与使用。

2.1 new/delete开辟内置类型空间

new:

//开辟一个指定类型大小的空间
int* ptr1 = new int;//为开辟的空间赋于指值
int* ptr2 = new int(10);//开辟连续n个指定类型大小的空间
int* ptr3 = new int[10];//连续空间的初始化赋值
int* ptr4 = new int[3]{1,2,3};

delete:

//释放大小为1个指定类型大小的空间
delete ptr1;//释放大小为多个指定类型空间大小的空间
delete[] ptr3

注: new/delete 与 new[]/delete[] 必须配合使用,不能混用

2.2 new/delete开辟销毁自定义类型空间

  1. new/delete开辟自定义类型的动态空间,会自动调用自定义类型的构造与析构函数
class A
{
private:int _a;
public:A(int a = 0):_a(a){cout << "A()" << endl;}A(const A& tmp){_a = tmp._a;cout << "A(const A&)" << endl;}	~A(){cout << "~A()" << endl;}
};int main()
{A* pa1 = new A;delete pa1;A* pa2 = new A[3];delete[] pa2;return 0;
}
  1. 开辟自定义类型空间的初始化方式
A aa1;
A aa2;
A aa3;//一段空间
//方法1:(用存在的对象)
A* pa1 = new A(aa1);
//方法2:(创建匿名对象)
A* pa2 = new A(A());
//方法3:(隐式类型转换,构造 + 拷贝构造,优化为构造)
A* pa3 = new A(3);//多段空间
//方法1:
A* pa4 = new A[3]{aa1, aa2, aa3};
//方法2:
A* pa5 = new A[3]{A(), A(), A()};
//方法3:(前三个元素初始化为1,2,3,后面会赋值的部分会全部默认初始化为0,类似数组)
A* pa6 = new A[10]{1, 2, 3};

3. operator new 与 operator delete函数

  1. new和delete是我们进行动态内存申请和释放的操作符,而其实现空间的开辟与释放的方式为,去调用用名为operator new 和operator delete的两个函数。
  2. 这两个函数是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间,接下来就让我们来学习这两个函数的相关知识。(虽然名为operator,但与运算符重载无关,两者为全局函数)
  3. operator new与operator delete的实现,底层为直接调用malloc与free来实现空间的动态开辟,其用法也与malloc/free相同。(operator new与operator delete两者可以直接调用)
int* pa = (int*)operator new(sizeof(int));*pa = 10;
cout << *pa << endl;operator delete(pa);
  1. 既然operator new/delete实现依旧是调用malloc/free实现动态开辟空间,那为什么不去直接使用malloc/free?
  2. malloc申请空间失败会返回0(NULL),此返回值不符合面向对象的编程特性,所以C++对其进行了一层封装,使得申请空间失败后抛出异常,为了与malloc的封装匹配,于是将free也进行了封装,封装为operator delete。
  3. 调用抛出异常演示:
//连续申请空间,申请空间不足,开辟失败
void func_test()
{char* c1 = new char[1024 * 1024 * 1024];//cout << c1 << endl;//char类型的变量,流插入操作自动识别时会默认识别为字符串而不是指针cout << (void*)c1 << endl;//捕获异常的操作会改变执行流,不再执行下面操作,而会抛出异常//类似gotochar* c2 = new char[1024 * 1024 * 1024];cout << (void*)c2 << endl;
}int main()
{try{func_test();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

4. new与delete的实现原理

  1. 申请开辟自定义类型空间时,new操作符会先开辟出指定大小的空间,而后调用构造函数初始化开辟出的空间。(申请空间 + 调用构造)

在这里插入图片描述

  1. delelte销毁自定义类型的申请空间时,会先调用自定义类型的析构函数,而后再进行空间的销毁释放。(调用析构,销毁空间)

在这里插入图片描述

  1. new/delete操作符在编译时会直接按照上述调用步骤,生成汇编指令。(汇编调试,call,jump)
  1. new调用new,申请时,会先开辟空间,再调用构造,构造时再调用new。销毁时,会先调用析构,析构先销毁里层new申请的空间,而后再销毁外层new申请的空间。

在这里插入图片描述

class Stack
{
private:int* _a;int _capacity;int _top;
public:Stack(int n = 4){cout << "Stack()" << endl;_a = new int[n];_top = 0;_capacity = 4;}~Stack(){cout << "~Stack()" << endl;delete[] _a;_top = 0;_capacity = 0;}
};int main()
{Stack* p1 = new Stack;delete[] p1;return 0;
}
  1. new/delete与new[]/delete[]不匹配使用可能会产生的风险与delete[]的工作原理:
    (汇编调试)
int main()
{Stack* p1 = new Stack[10];delete[] p1;return 0;
}

在这里插入图片描述

  1. 10个Stack类型的变量组成的空间大小应为120字节,可是,经过汇编调试后,大小却为124字节。这是因为,new[]申请连续的自定义类型空间时,会额外在头部申请一个四字节大小的空间用来存放申请的自定义类型空间个数,delete[]在析构时会向前调整四个字节,读取需要调用析构的次数。(delete获取需要调用析构的次数)

在这里插入图片描述

  1. 当使用delete去释放new[] 出的空间时,不会向前调整四个字节,而是调用一次析构函数后,从第一个元素的首地址释放空间,会导致内存泄漏。
  1. 编译器的优化:因为类A的成员变量只有一个int变量,且构造时没有开辟额外空间,当我们将类A的析构函数屏蔽后(没有使用析构的必要),当再次new[]一段连续空间时,将不再于头部开辟额外空间存储元素个数。delete所要做的就只是释放开辟的空间,这一点,使用free也同样可以做到。
class A
{
private:int _a;
public:A(){cout << "A()" << endl;}/*~A(){cout << "~A()" << endl;}*/
};int main()
{A* pa = new A[10];//用delete去销毁new[]申请的空间delete pa;//free(pa);return 0;
}

5. 定位new表达式与池化计数

  1. 在前面的学习中我们了解到,类的析构函数可以显示调用,而构造函数却不可以。那么,当我们不使用new的方式开辟出自定义类型的空间后,有没有办法对这段空间使用构造函数进行初始化呢,接下来,我们引入定位new表达式。
//调用默认构造
new(指针:指定空间地址)类型
//赋予初始值
new(指针)类型(初始值)

示例:

class A
{
private:int _a;
public:A(int a = 0):_a(a){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}
};int main()
{A* pa1 = (A*)operator new(sizeof(A));//调用构造new(pa1)A;//析构pa1->~A();//释放空间operator delete(pa1);A* pa2 = (A*)operator new(sizeof(A));//调用构造,并指定初始值new(pa1)A(10);pa2->~A();operator delete(pa2);return 0;
}
  1. 定位new应用场景:
    <1> 因为new申请动态开辟的空间是在堆上,很多时候我们需要频繁的调用申请空间,这样的效率非常低。
    <2> 所以,C++中创建了内存池来应对这一类问题,先提前申请出一大块空间备用,这块空间被称为内存池,当我们需要申请空间时,不用再去堆上申请,而是可以直接找内存池申请划分。
    <3> 这些申请来的空间(自定义类型)是已经开辟好的,这些空间没有调用构造函数进行初始化,无法使用,而定位new就可以解决这样的问题。

6. malloc/free与new/delete的异同

相同点:

  1. 都是于堆区上开辟空间,且都需要手动释放

不同点:

  1. malloc申请的空间不会进行初始化,而new申请出的空间可以进行初始化
  2. malloc申请空间需要计算空间大小,而new只需要指定对象个数
  3. malloc的返回必须要强转为对应类型的指针,而new在申请空间时就声明了类型
  4. malloc申请空间失败返回空,new申请失败抛出异常
  5. malloc开辟销毁空间时不会调用构造,析构函数,而new/delete开辟销毁空间时会调用构造与析构

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

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

相关文章

表结构 / 字段操作

一.增 alter table emp add wickname varchar(20); 二.改 1.仅仅改变数据类型&#xff08;字段类型&#xff09; alter table emp modify wickname varchar(30); 2.字段名 和 字段类型 都改变 alter table emp change wickname username varchar(20); 三.删 alter table emp …

ZigBee技术与实训教程(无线传感网技术第五天持续更新)

ZigBee具有广阔的应用前景。 家庭和楼宇网络。工业控制。公共场所。农业控制。医疗。商业。 1.ZigBee的协议框架 Zigbee栈是在IEEE 802.15.4标准基础上建立的&#xff0c;定义了MAC层和PHY&#xff08;数据链路层&#xff09;。ZigBee设备还包括IEEE 802.15.4(该标准定义了r…

0104行列式的性质-行列式-线性代数

记 D ∣ a 11 a 12 ⋯ a 1 n a 21 a 22 ⋯ a 2 n ⋯ ⋯ ⋯ a n 1 a n 2 ⋯ a n n ∣ D\begin{vmatrix}a_{11}&a_{12}&\cdots &a_{1n}\\a_{21}&a_{22}&\cdots&a_{2n}\\\cdots&\cdots&&\cdots\\a_{n1}&a_{n2}&\cdots&a_{nn}\en…

Spring神器:SpEl表达式

SpEl使用 Spring Expression Language (SpEL) 是Spring框架提供的一种强大的表达式语言&#xff0c;它允许开发人员在运行时动态地计算表达式&#xff0c;并访问和操作对象图。SpEL是一种基于表达式的语言&#xff0c;它支持各种操作符、函数和变量&#xff0c;可以用来执行各…

WebGL之灯光使用解析

在使用灯光之前&#xff0c;首先我们需要了解&#xff0c;与定义更广泛的 OpenGL 不同&#xff0c;WebGL 并没有继承 OpenGL 中灯光的支持。所以你只能由自己完全得控制灯光。幸运得是&#xff0c;这也并不是很难&#xff0c;本文接下来就会介绍完成灯光的基础。 在 3D 空间中…

使用链表的优先级队列

优先级队列是一种特殊类型的队列&#xff0c;其中每个元素都是与优先级相关联&#xff0c;并根据其优先级提供服务。如果元素有相同的优先级&#xff0c;那么根据它们在队列中的排列顺序。 元素本身的值可以用于分配优先级。例如&#xff1a;最高值的元素被视为最高的优先级元素…

多线程锁.

公平锁与非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的Lock lock new Reentrantlock(true);/true表示公平锁,先来先得非公平锁指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请…

类和对象 (中)

文章目录 类的六个默认成员函数构造函数析构函数特性使用 总结构造函数和析构函数拷贝构造函数特性拷贝构造总结 赋值运算符的重载运算符重载赋值运算符重载总结拷贝构造函数和赋值运算符重载 关于operator<<重载日期类实现const 修饰的成员函数取地址重载以及const取地址…

css实现高度是宽度一半的效果

1、方法一&#xff1a;使用变量:root、var()、clac()实现&#xff1a; 1.1 效果如下&#xff1a; 2.2 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title>&l…

伸手党必备之Python正则表达式常用函数

今天介绍一下Python中常用的正则表达式处理函数。Python的正则表达式主要有两种方法完成模式匹配&#xff1a;『搜索』和『匹配』 re.match re.match 尝试从字符串的开始全部或者部分匹配某个模式&#xff0c;如&#xff1a;下面的例子匹配第一个单词。 import re text &…

springboot269反欺诈平台的建设

反欺诈平台设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装反欺诈平台软件来发挥其高效地信息处…

Vue-Vben-Admin:中大型项目后台解决方案及如何实现页面反向传值

Vue-Vben-Admin&#xff1a;中大型项目后台解决方案及如何实现页面反向传值 摘要&#xff1a; Vue-Vben-Admin是一个基于Vue3.0、Vite、Ant-Design-Vue和TypeScript的开源项目&#xff0c;旨在为开发中大型项目提供一站式的解决方案。它涵盖了组件封装、实用工具、钩子函数、动…

学习c语言:单链表的应用

一、单链表经典算法 1.1 单链表相关经典算法OJ题1&#xff1a;移除链表元素 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.…

linux 新增用户 adduser

这里写自定义目录标题 linux 新增用户 Reference: https://dontla.blog.csdn.net/article/details/128723451

WPF监控平台(科技大屏)[一]

跟着B站的视频敲了一个略微复杂的WPF界面,链接如下.在这里我详细的写一份博客进行设计总结. 系统介绍和配置及主窗口设计_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Wy421Y7QD?p1&vd_source4796b18a2e4c1ec8a310391a5644b6da 成果展示 实现过程 总体来说,我的…

蓝桥杯---附近最小(典型的滑动窗口类型问题)

题目链接&#xff1a;附近最小 import java.util.ArrayDeque; import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {static int n;static int[] a;static int k;public static void main(String[] args) {Scanner scannernew Scanner(…

vue - - - - 数据刷新试图不刷新?是不外层有个table?

<table> // ...<customCell :_dataSourcedataSource/>// ... </table>dataSource 更新时&#xff0c;自定义组件对应的是图没有更新 解决办法&#xff1a; 给table加一个变化的key <table key"randomKey"> // ...<customCell :_dataSou…

OpenHarmony开源项目—工程管理

DevEco Studio的基本使用&#xff0c;请参考DevEco Studio使用指南。本章主要介绍如何使用DevEco Studio进行多设备应用开发。 说明&#xff1a; 本章的内容基于DevEco Studio 3.1.1 Release版本进行介绍&#xff0c;如您使用DevEco Studio其它版本&#xff0c;可能存在文档与产…

微信小程序(五十八)分步表单多页面传值

注释很详细&#xff0c;直接上代码 新增内容&#xff1a; 1.分步表单传值 2.伪数据生成 源码&#xff1a; app.json {"pages": ["pages/index/index","pages/building/building","pages/room/room","pages/logs/logs"],&qu…

【项目经验】Redis Sentinel从工程中下线并对业务迁移-进行中

一、背景&#xff1a; 某天&#xff0c;接到DBA通知&#xff0c;Redis sentinel 只支持到3.2.X(这个命题有问题&#xff0c;往下翻&#xff0c;见彩蛋)&#xff0c;为节省运维成本&#xff0c;提升运维效率&#xff0c;决定将工程中使用的Redis sentinel下线&#xff0c;都使用…