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),字符串长度,4
    char 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;

    //捕获异常的操作会改变执行流,不再执行下面操作,而会抛出异常
    //类似goto
    char* 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开辟销毁空间时会调用构造与析构

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

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

相关文章

Spring MVC中的REST风格

文章目录 REST风格1 REST简介问题导入1.1 REST介绍1.2 RESTful介绍1.3 注意事项 2 RESTful入门案例问题导入2.1 快速入门2.2 PathVariable介绍2.3 RequestBody、RequestParam、PathVariable区别和应用 3 REST快速开发【重点】3.1 代码中的问题3.2 Rest快速开发 4案例&#xff1…

算法时空复杂度分析:大O表示法

文章目录 前言大O表示法3个时间复杂度分析原则常见的时间复杂度量级空间复杂度参考资料 前言 算法题写完以后&#xff0c;面试官经常会追问一下你这个算法的时空复杂度是多少&#xff1f;&#xff08;好像作为一名算法工程师&#xff0c;我日常码代码的过程中&#xff0c;并没…

FreMIM:傅里叶变换与遮罩的图像建模在医学图像分割中的应用

代码链接&#xff1a;GitHub - Rubics-Xuan/FreMIM: This repo holds the official code for the paper "FreMIM: Fourier Transform Meets Masked Image Modeling for Medical Image Segmentation". 论文链接&#xff1a;https://arxiv.org/abs/2304.10864 收录于…

C#重新认识笔记_ FixUpdate + Update

C#重新认识笔记_ FixUpdate Update Update: 刷新频率不一致,非物理对象的移动&#xff0c;简单的刷新可用&#xff0c; FixedUpdate: 刷新频率一致,按照固定频率刷新&#xff0c;一般调用FixedUpdate之后&#xff0c;会立即进入必要的物理计算中,因此&#xff0c;任何影响刚…

springboot3 打包报错32-bit architecture x86 unsupported或者 returned non-zero result

springboot3 打包异常情况处理记录 在测试springboot3 native打包时候遇到的异常&#xff0c;百度和谷歌上方法都无法解决我的问题&#xff0c;最后记录一下我最后的原因和解决方案。 前置要求&#xff1a;自己处理好vs的相关内容后 报错一&#xff1a; [1/7] Initializing…

回归测试,有什么高效的测试方法?

什么是回归测试&#xff1f; 回归测试&#xff08;Regression testing&#xff09; 指在发生修改之后重新测试先前的测试以保证修改的正确性。理论上&#xff0c;软件产生新版本&#xff0c;都需要进行回归测试&#xff0c;验证以前发现和修复的错误是否在新软件版本上再次出现…

跨境电子商务支付与结算的支撑系统

​1、跨境电子商务支付与结算的核心系统。 核心系统是用户执行跨境电子商务支付的核心模块&#xff0c;包括以下具体流程。 ​ ​①用户从跨境电子商务支付应用启动跨境电子商务支付流程。 ②跨境电子商务支付应用根据应用和用户选择的支付工具&#xff0c;来调用对应的支付产…

来吧伙计们,让AI教我们怎么说海盗语

“如果想伺机而动&#xff0c;就是这样。”——杰克船长提到海盗&#xff0c;我们往往联想到约翰尼德普在《加勒比海盗》中饰演的杰克船长。我们有什么理由不喜欢海盗呢&#xff1f;他们航行在海上&#xff0c;寻找埋藏的宝藏&#xff0c;痛饮朗姆酒&#xff0c;用自己独特的海…

24考研调剂 | 武汉纺织大学

教育部重点实验室招收24年调剂生&#xff0c;材料、化学、机械工程、计算机、力学等相关专业 考研调剂招生信息 学校:武汉纺织大学 专业:工学->材料科学与工程 年级:2024 招生人数:100 招生状态:正在招生中 联系方式:********* (为保护个人隐私,联系方式仅限APP查看)…

springboot的maven多模块如何混淆jar包

springboot的maven多模块如何混淆jar包 一.简介二. 示例2.1 基本配置2.2 结果 三. 错误3.1 错误13.2 错误2 四. 参考文章 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 一.简介 …

王道机试C++第6章 数学问题和22年蓝桥杯省赛选择题Day34

6.1 进制转换 二进制数&#xff08;十转二&#xff09; 习题描述 大家都知道&#xff0c;数据在计算机里中存储是以二进制的形式存储的。 有一天&#xff0c;小明学了C语言之后&#xff0c;他想知道一个类型为unsigned int 类型的数字&#xff0c;存储在计算机中的二进制串是…

个人博客系统(测试报告)

一、项目背景 一个Web网站程序&#xff0c;你可以观看到其他用户博客也可以登录自己的账号发布博客&#xff0c;通过使用Selenium定位web元素、操作测试对象等方法来对个人博客系统的进行测试&#xff0c;测试的核心内容有用户登录、博客列表及博客数量的展示、查看全文、写博客…

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

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

Python逆向:pyc字节码转py文件

一、 工具准备 反编译工具&#xff1a;pycdc.exe 十六进制编辑器&#xff1a;010editor 二、字节码文件转换 在CTF中&#xff0c;有时候会得到一串十六进制文件&#xff0c;通过010editor使用查看后&#xff0c;怀疑可能是python的字节码文件。 三、逆向反编译 将010editor得到…

链路聚合实验(思科)

华为设备参考&#xff1a; 一&#xff0c;技术简介 网络设备的链路聚合技术&#xff08;Link Aggregation&#xff09;是一种将多个物理链路捆绑在一起&#xff0c;形成一个逻辑链路的技术。这样做可以增加带宽、提高可靠性和实现负载均衡。 二&#xff0c;实验目的 橙色的阻…

使用Sourcetree推送本地仓库至远程仓库时报错The host key is not cached for this server

原因是SSH没配置好 点击工具→选项→ 改成OpenSSH&#xff0c;密钥改成配置Git和本地仓库时生成的.ssh文件夹下的id_rsa文件。

Spring boot 集成netty实现websocket通信

一、netty介绍 Netty 是一个基于NIO的客户、服务器端的编程框架&#xff0c;使用Netty 可以确保你快速和简单的开发出一个网络应用&#xff0c;例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程&#xff0c;例如&#xff1a;基于TCP和U…

力扣-[700. 二叉搜索树中的搜索]

递归法 确定递归函数的参数和返回值 递归函数的参数传入的就是根节点和要搜索的数值&#xff0c;返回的就是以这个搜索数值所在的节点。 代码如下&#xff1a; public TreeNode searchBST(TreeNode root, int val) 确定终止条件 如果root为空&#xff0c;返回null&#xff0c…

【前端】HTML常用标签

因为想当个全栈&#xff0c;所以巩固了一下HTML与CSS和JS基础&#xff0c;这一篇博客是HTML部分 文章目录 HTML 基础标签 1HTML 基础框架HTML 基础标签语义标签文本格式化标签div 与 span 标签图像标签超链接特殊字符 基础标签 2 | 表格表格的使用表格标签表格属性表格的头部与…

JavaEE:网络编程

网络编程&#xff1a;通过代码完成基于网络的跨主机通信 跨主机通信方式&#xff1a; 1.TCP/IP网络 2.蓝牙通信 3.近场通信NFC 4.毫米波通信&#xff1a;功率高&#xff0c;带宽高&#xff0c;抗干扰能力差 其中TCP/IP网络是日常编程中最常涉及到的&#xff0c;最通用的跨主机通…