【C++初阶】C/C++内存管理(没有对象的都进来看看吧~)

news2024/11/26 18:40:38

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、C/C++内存分布
  • 二、C语言中动态内存管理
    • 2.1 面试题
  • 三、C++内存管理方式
    • 3.1 new/delete操作内置类型
    • 3.2 new和delete操作自定义类型
  • 四、operator new与operator delete函数(重点)
    • 4.1 介绍operator new与operator delete函数
    • 4.2 new和delete的底层原理
  • 五、new和delete的实现原理
    • 5.1 内置类型
    • 5.2 自定义类型
  • 六、定位new表达式(placement-new) (了解)
  • 七、常见面试题
    • 7.1 malloc/free`和`new/delete的区别
    • 7.2 内存泄漏
    • 7.2.1 什么是内存泄漏
    • 7.2.2 内存泄漏的危害
    • 7.2.3 内存泄漏分类(了解)
    • 7.2.4 如何避免内存泄漏

一、C/C++内存分布

首先回顾,数据是如何在内存中存储的:

在这里插入图片描述

接着可以来看看以下的题目:

在这里插入图片描述

【选择题】

选项: 
A.(局部变量)  
B.堆  
C.静态区(全局变量和静态变量)  
D.代码段(常量区)

1. a在哪里? 
2. static_b在哪里? 
3. static_c在哪里?
4. d在哪里?
5. arr1在哪里?
6. arr2 在哪里?
7. *arr2在哪里?
8. arr3在哪里?
9. *arr3在哪里?
10. ptr1在哪里?
11. *ptr1在哪里?

【答案 + 解析】

在这里插入图片描述

【填空题】

1. sizeof(arr1) = ____;
2. sizeof(arr2) = ____; 
3. sizeof(arr3) = ____; 
4. sizeof(ptr1) = ____;
5. strlen(arr2) = ____;
6. strlen(arr3) = ____;

【答案+ 解析】

在这里插入图片描述

【问答题】

sizeofstrlen区别?

  • strlen函数用于计算字符串的长度,只统计字符串中字符的数量,不包括结尾的空字符。
  • sizeof操作符用于计算变量或类型的大小,一般单位为字节,通常用于计算内存大小。

若还想做以上类似题目 —> [点击跳转]

二、C语言中动态内存管理

往期回顾:[C语言中动态内存管理]

#include <iostream>
#include <stdlib.h>
using namespace std;

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

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

	return 0;
}

问:这里需要free(p2)吗?
答案当然是不需要的。realloc功能是在已有的内存空间扩容,然而扩容会存在原地扩容和异地扩容(当原有空间之后没有足够大的空间)。但是扩容后,返回的是调整之后的内存的起始地址,也就p2指向的地址,因此只要释放了p3p2自然而然就被释放掉了。

2.1 面试题

  1. malloc/calloc/realloc的区别?
  • malloccalloc都是向内存申请空间,不同的是malloc分配的内存区域中的初始值是随机的,而calloc会将分配的内存区域中的初始值全部初始化为0
  • realloc可以扩大原有的内存空间,而扩容的方式有2种,一种是原地扩容(在已有的内存空间后扩容);还有一种是异地扩容(当原有空间之后没有足够大的空间),它的扩容方式是:它会在内存空间重新开辟一块空间,然后 把原有的内存空间中的数据复制到新的空间,最后再释放原有的内存空间
  1. malloc的实现原理
  1. 维护一个空闲内存块链表,其中每个内存块都记录了其大小和是否已被分配。
  2. 当程序调用malloc函数请求分配一块内存时,遍历空闲内存块链表,找到第一个大小足够的内存块并将其从链表中移除。
  3. 如果找到的内存块比请求的内存大,则将其分裂成两个部分,一个用于满足请求,另一个用于将来的分配请求。
  4. 如果没有找到足够大小的内存块,则向操作系统请求更多的内存。
  5. 将已分配的内存块的大小和状态记录下来,并将其返回给程序。

需要注意的是,malloc的实现可能因操作系统和编译器而异,但其基本原理都是以上述步骤为基础的。同时,为了避免内存泄漏和内存碎片化等问题,malloc通常还会实现一些额外的功能,如内存池、缓存、对齐等。

三、C++内存管理方式

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

3.1 new/delete操作内置类型

在这里插入图片描述

#include <iostream>
using namespace std;

int main()
{
	// C语言版
	int* p1 = (int*)malloc(sizeof(int));
	free(p1);

	// C++版 new + delete
	// 用法: new + 类型
	int* p2 = new int;
	delete p2; // 释放

	// 开辟40个字节,也就是10个整型
	// C语言版
	int* p3 = (int*)malloc(sizeof(int) * 10);
	free(p3);

	// C++版
	int* p4 = new int[10];
	delete[] p4;

	return 0;
}

当然,C++中的new还能对空间进行初始化

#include <iostream>
using namespace	std;

int main()
{

	int* p4 = new int[10]{ 1, 2 ,3 };
	for (int i = 0; i < 10; i++)
	{
		cout << p4[i] << ' ';
	}
	cout << endl;
	delete[] p4;

	int* p5 = new int(10);

	cout << *p5 << endl;
	delete p5;

	return 0;
}

【程序结果】

在这里插入图片描述

注意:

  • p4是申请10int的数组,对于数组要用{}初始化;p5是申请1个数组,初始化为10,用()初始化
  • 若不进行初始化,newmalloc一样,开辟的空间都是 随机值
    在这里插入图片描述
  • newdelete操作符需要 匹配 起来使用,不能与mallocfree混用

3.2 new和delete操作自定义类型

new/deletemalloc/free最大区别是:new/delete对于自定义类型除了开空间,还会调用构造函数(new)和析构函数delete)。

#include <iostream>
#include <stdlib.h>
using namespace std;

struct ListNode
{
public:
    // 构造函数
    ListNode(int x)
        :_val(x)
        , _next(nullptr)
    {
        cout << "调用了构造函数" << endl;
    }
    // 析构函数
    ~ListNode()
    {
        cout << "调用析构函数" << endl;
    }

    int _val;
    struct ListNode* _next;
};

struct ListNode* BuyListNode(int x)
{
    // 单纯开空间
    // C语言版太太太长了
    struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
    if (newnode == nullptr)
        return nullptr;
    newnode->_next = nullptr;
    newnode->_val = x;

    return newnode;
}

int main()
{
    // C语言版
    struct ListNode* n1 = BuyListNode(1);
    struct ListNode* n2 = BuyListNode(2);
    struct ListNode* n3 = BuyListNode(3);

    // 开空间+调用构造函数初始化
    ListNode* nn1 = new ListNode(1);
    ListNode* nn2 = new ListNode(2);
    ListNode* nn3 = new ListNode(3);

    delete nn1;
    delete nn2;
    delete nn3;

    return 0;
}

【调试结果 + 程序运行结果】

在这里插入图片描述

在这里插入图片描述

四、operator new与operator delete函数(重点)

4.1 介绍operator new与operator delete函数

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

以下是operator newoperator new的源码

/*
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)
// 是个宏

通过以上两个全局函数,我们发现:

  1. operator new函数实际是通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
  2. operator new函数本质是封装了mallocoperator delete本质是封装了free

以下是operator newoperator delete的使用方法:

// operator new和operator new是库函数提供的
// 因此可以直接使用
// 用法和malloc、free是一样的
#include <iostream>
using namespace std;

int main()
{
	int* p1 = (int*)operator new(sizeof(int));
	operator delete(p1);

	return 0;
}

通过以上代码我们发现:operator newoperator delete的用法和功能都分别与mallocfree一样。它们都不会去调用构造函数和析构函数,不过还是有区别的:

  1. operator new不需要检查开辟空间的合法性(不需要特别判断p1的返回值)。
  2. operator new开辟空间失败就抛异常(对于面向对象语言处理失败,不喜欢用返回值,建议用抛异常)。
    在这里插入图片描述

4.2 new和delete的底层原理

  • new的底层原理就是转换成调用operator new + 构造函数,我们可以通过查看反汇编来验证:
#include <iostream>
using namespace	std;

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

int main()
{
	A* a1 = new A;
	delete a1;

	return 0;
}

【反汇编】

在这里插入图片描述

  • 同样的,delete的底层原理:转换成调用operator delete + 析构函数

【反汇编】

在这里插入图片描述

因此,我们就可以得到new、operator new、mallocdelete、operator delete、free之间的关系:

在这里插入图片描述

五、new和delete的实现原理

5.1 内置类型

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

5.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表达式是 在已分配的原始内存空间中调用构造函数初始化一个对象。

  • 使用格式
  1. new(指针) + 类型
  2. new (指针) type(类型的初始化列表)
  • 使用场景

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

#include <iostream>
using namespace std;

class A
{
public:
	// 构造函数
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}

	// 析构函数
	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

// 定位new/replacement new
int main()
{
	// p1指向一块内存空间,但空间未被初始化
	A* p1 = (A*)malloc(sizeof(A));

	// 定位new表达式:new(指针) + 类型
	// 注意:如果A类的构造函数有参数时,此处需要传参
	new(p1)A; 
	
	// 调用析构函数
	p1->~A();
	// p1的空间是malloc出来的
	// 因此要用free来释放
	free(p1);

	A* p2 = (A*)operator new(sizeof(A));
	new(p2)A(10);
	p2->~A();
	operator delete(p2);

	return 0;
}

七、常见面试题

7.1 malloc/freenew/delete的区别

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

7.2 内存泄漏

7.2.1 什么是内存泄漏

内存泄漏指:因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费

7.2.2 内存泄漏的危害

长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

7.2.3 内存泄漏分类(了解)

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc/calloc/realloc/new等从堆中分配的一块内存,用完后必须通过调用相应的free或者delete删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生堆内存泄漏

  • 系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

7.2.4 如何避免内存泄漏

  1. 事前预防型。如 智能指针 等。
  2. 事后查错型。如泄漏检测工具

1. linux下内存泄漏检测工具
3. 在windows下使用第三方工具:VLD工具说明
3. 其他工具:内存泄漏工具比较

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

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

相关文章

2023 华为 Datacom-HCIE 真题题库 08/12--含解析

单项选择 1.[试题编号&#xff1a;190385] &#xff08;单选题&#xff09;以下关于BGP/MPLSIPVPN路由交互的描述&#xff0c;错误的是哪一项? A、PE与CE之间交互的是IPv4路由信息 B、出口PE可以通过BGP、IGP或静态路由的方式向远端CE发送IPv4路由 C、入口PE将从CE接收到的I…

ODOO随笔(二)—— Odoo16的主题样式变更

1 登陆界面的修改 系统默认的登陆界面&#xff0c;有更改odoo logo和去除“管理数据库”、“由Odoo提供支持”两个需求。 &#xff08;1&#xff09;更改odoo logo 系统管理员登陆后&#xff0c;选择菜单栏&#xff1a;设置——公司——管理公司 点击相机图标&#xff0c;上传…

Round#13 web专项部分wp

flask?jwt? 忘记密码处有secretkey 然后就是伪造session了,这里猜一下要什么 最后应该是_is_admin有用,我没细看,当时平台卡麻了 指正:改_user_id为1 ez_factors 注意到可以拼接命令,但是执行的回显只有数字 那么可以考虑读取后用od命令来转8进制读取 flask?jwt?(hard) …

2023.5.30 深信服 c++ 一面

深信服&#xff0c;c一面小记 导语面试内容重点问题解析弱引用弱在哪里&#xff1f;手撕memcpy水壶倒水问题 导语 最近开始面试&#xff0c;记录一下面试经历。   应该是会给我发感谢信的吧~我也是真的菜。工作的原因其实也没时间准备&#xff0c;最近工作还是挺忙的。另外一…

【云原生docker】

容器化越来越受欢迎&#xff0c;因为容器是&#xff1a; ●灵活&#xff1a;即使是最复杂的应用也可以集装箱化。 ●轻量级&#xff1a;容器利用并共享主机内核。 ●可互换&#xff1a;可以即时部署更新和升级。 ●便携式&#xff1a;可以在本地构建&#xff0c;部署到云&#…

LVGL学习(2):图片的转换和显示

我们在设计UI的过程中可能需要显示一些图片&#xff0c;本篇文章将介绍如何转换并显示一个固定的图片到lv_img中。 文章目录 1 图片转换1.1 GUI Guider1.2 在线转换 2 图片的显示 1 图片转换 和之前我写的一篇字体转换的文章一样&#xff1a;LVGL学习(1)&#xff1a;中文字体…

基于牛顿拉夫逊的配电网潮流计算研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

C++语法(23)-- 模拟实现unordered_set和unordered_map

C语法&#xff08;22&#xff09;---- 哈希表的闭散列和开散列_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/130436178?spm1001.2014.3001.5501 1.重写HashTable 由于此时我们的实现与map跟set差不多&#xff0c;所以需要进行调整 1.重写节点…

html5前端学习

HTML5基本骨架 html标签 定义HTML文档&#xff0c;浏览器看到后就明白这个是HTML文档&#xff0c;所以其他元素要包裹在它里面&#xff0c;标签限定了文档的开始点和结束点。 <!DOCTYPE html> <html> </html> head标签 head标签用于定义文档的头部&…

实验篇(7.2) 05. 通过浏览器访问远端内网服务器 (SSL) ❀ 远程访问

【简介】直接将内网服务器映射成公网IP&#xff0c;可以方便的从任何地方访问服务器的指定端口&#xff0c;但是这种方式下&#xff0c;服务器是公开且暴露的。那有没有即方便、又比较安全的远程访问服务器的方法呢&#xff1f;我们来看看SSL VPN的Web模式。 SSL VPN介绍 从概念…

波司登云原生微服务治理探索

作者&#xff1a;曾孟琪&#xff08;山猎&#xff09; 背景 波司登创始于1976年&#xff0c;专注于羽绒服的研发、设计、制作&#xff0c;是全球知名的羽绒服生产商。波司登用一系列世人瞩目的辉煌成绩证明了自己的实力&#xff1a;连续26年全国销量领先&#xff0c;连续22年…

4种事务隔离级别 3种异常现象 死锁

4种事务隔离级别 & 3种异常现象 4种事务隔离级别和3种异常现象 事务隔离级别是指多个并发事务之间相互隔离的程度&#xff0c;用于控制事务对数据库的读取和写入操作的可见性和影响范围。在关系数据库管理系统&#xff08;RDBMS&#xff09;中&#xff0c;常见的事务隔离…

70. 爬楼梯解题思路

文章目录 题目解题思路 题目 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1 阶 …

【原创】用 VisualGLM 进行AIGC多模识别和内容生成

最近几个月&#xff0c;整个AI行业的LLM&#xff08;大语言模型&#xff09;蓬勃发展&#xff0c;除了过去传统的纯文字的多模态能力的视觉语言模型&#xff0c;如 GPT-4&#xff0c;ImageBind等表现令人印象深刻。 ChatGLM-6B是中文用户使用非常舒服的一个开源中文LLM。2023年…

Nginx的Rewrite的运用

Rewrite 一、常用的Nginx正则表达式二、lication三、rewrite$request_uri&#xff1a;包含请求参数的原始URI&#xff0c;不包含主机名&#xff0c;如&#xff1a;http://www.kgc.com/abc/bbs/index.html?a1&b2 中的 /abc/bbs/index.php?a1&b2 $uri&#xff1a;这个变…

chatgpt赋能python:Python去除重复字符并排序

Python去除重复字符并排序 在日常编程工作中&#xff0c;我们常常需要对列表或字符串中的重复字符进行去除&#xff0c;并将剩余字符进行排序。Python是一门非常适合进行此类操作的语言&#xff0c;其内置的数据结构和函数可以让我们轻松应对这一需求。 介绍 当我们需要对一…

【六一】【海思SS528】GPIO寄存器操作 - 使能GPIO管脚输出高、低电平

目录 一、概述二、配置复用控制寄存器&#xff0c;使能GPIO功能三、配置GPIO_DIR寄存器&#xff0c;选择输出四、配置GPIO_DATA寄存器&#xff0c;输出高电平五、测试 一、概述 这篇文章根据海思SS528芯片提供的《22AP30 H.265编解码处理器用户指南.pdf》文档(文档路径&#xf…

MIT6.042学习笔记(一)——强归纳法,数论(1)

如果学生在学校里学习的结果是使自己什么也不会创造&#xff0c;那他的一生永远是模仿和抄袭。——列夫托尔斯泰 文章目录 引言强归纳法例题&#xff1a;拆积木游戏证明 数论&#xff08;第一节&#xff09;整除运算例题&#xff1a;取水证明 公约数例题证明 Eucild算法 引言 …

C语言-static的用法

1、static 关键字 C 语言中 static 关键字修饰变量和函数。 static有三种不同的用法&#xff1a; 1.修饰局部变量&#xff1b; 2.修饰全局变量&#xff1b; 3.修饰函数 局部变量:当函数第一被调用&#xff0c;函数中的静态局部变量被初始化&#xff0c;再次调用这个函数&…

gcc/g++

文章目录 sudo 提权添加白名单gcc / g预处理编译汇编链接 sudo 提权添加白名单 1.寻找root用户 在 /etc/sudoers 文件中修改 添加普通用户的白名单 :/root找到root的白名单所在行数 2.wq!强制保存退出&#xff0c;即可添加成功 gcc / g 推荐写法 gcc mycode.c -o mytest预…