动态内存管理:new和delete的底层探索

news2024/12/27 4:41:26

之前我们在C语言上是学过malloc和calloc还要realloc等函数来在堆上获取相应的内存,但是这些函数是存在缺陷的,今天引入对new和delete的学习,来了解new和delete的底层实现。

首先就是在C++中我们为什么要对内存进行区域的分块? 

答案是为了对内存进行更好的管理

那这些区域中对我们程序员来说最重要的区域就是堆,因为堆上的空间需要我们自行的进行申请和释放。

C语言内存题目

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" ;
const 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 );
}
1. 选择题:
  选项 : A .   B .   C . 数据段 ( 静态区 )   D . 代码段 ( 常量区 )
  globalVar 在哪里? ____   staticGlobalVar 在哪里? ____
  staticVar 在哪里? ____   localVar 在哪里? ____
  num1 在哪里? ____
  char2 在哪里? ____   * char2 在哪里? ___
  pChar3 在哪里? ____       * pChar3 在哪里? ____
  ptr1 在哪里? ____         * ptr1 在哪里? ____
2. 填空题:
  sizeof ( num1 ) = ____ ;  
  sizeof ( char2 ) = ____ ;       strlen ( char2 ) = ____ ;
  sizeof ( pChar3 ) = ____ ;     strlen ( pChar3 ) = ____ ;
  sizeof ( ptr1 ) = ____ ;

栈主要存放的是一些局部变量,函数参数,栈的最大特点就是向下增长,而堆是向上增长的,函数执行之后栈的的空间会自动的进行释放,函数栈帧的销毁其实就是将内存空间返回给我们的操作系统这个过程。栈还有一个特点就是它的效率高且容量空间有限

堆是给程序员进行管理的一块内存,堆是向上进行增长的,程序员进行使用之后需要进行释放,如果不进行释放就会存在很大的问题,内存泄漏是最主要的一个问题。分配的方式可能不是连续的,类似链表这样随机化。

代码段

存放常量和一些可读的代码

静态区
存放的是一些全局常量和静态数据 

那有了上面的基础我们就来完成上面的题目。

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";
const 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);
}
1. 选择题:
  选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
  globalVar在哪里?_C___   staticGlobalVar在哪里?__C__
  staticVar在哪里?__C__   localVar在哪里?_A___
  num1 在哪里?__A__

  char2在哪里?_A___   *char2在哪里?_A__
  pChar3在哪里?____ A     *pChar3在哪里?___D_
  ptr1在哪里?____   A     *ptr1在哪里?_B___
2. 填空题:
  sizeof(num1) = 40____;  
  sizeof(char2) = _5___;      strlen(char2) = ___4_;
  sizeof(pChar3) = __4_/ 8_;     strlen(pChar3) = __4__;
  sizeof(ptr1) = _4_/8__;

sizeof(指针)是指针大小就是4字节或者8字节,然后就要注意的是字符串后面还是有一个\0这个需要注意一下。

C++内存管理方式

C 语言内存管理方式在 C++ 中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++ 又提出了自己的内存管理方式: 通过 new delete 操作符进行动态内存管理
我们先来看看new和delete是怎么使用的。
#include<iostream>
int main()
{
	int* p1 = new int;
	int* p2 = new int[10];
    delete p1;
    delete[] p2;
	return 0;
}

优势一

竟然学了new和delete,抛弃原来的malloc和realloc还有delete,那总有我们的优势,但是我们现在可以看到的优势好像就是除了简短,没有其他的优势了,它也不会进行初始化,没有将我们的内置类型进行初始化,但是这里我们只要知道它的优势就是代码变的更加简短了。这个是用法上的

 优势二

我们可以进行手动的初始化

 看下面一段代码我们可以看到它的优势更加明显了

场景:

如果我们要实现一个链表的话,C语言是要写一个创造节点的函数,然后再来进行实现的,我们先来看看创造节点的函数是怎么写的,然后再来看看C++中如果要进行创造一个链表的话是怎么实现才是最快的。

struct ListNode
{
	ListNode* _next;
	int _val;
};

ListNode* CreateNode(int x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);

	}
	newnode->_next = NULL;
	newnode->_val = x;
	return newnode;
}

我们可以看到的是需要我们自行的创造节点,然后进行链接,可以写一个for循环,但是每次都要去调用这个函数,很不方便,这个时候就要引出new的第三个优势。

优势三

对于自定义类型的时候new的过程是先开一段空间,然后调用它的构造函数进行初始化。我们来快速的实现一个链表的连接,看看C++里是怎么写的。

#include<iostream>

using namespace std;
struct ListNode
{
	ListNode* _next;
	int _val;
	ListNode(int val)
		:_next(nullptr)
		,_val(val)
	{}
};

ListNode* CreateList(int n)//n表示的是长度
{
	ListNode head(-1);
	int val = 0;
	ListNode* tail = &head;
	for (int i = 0; i < n; i++)
	{
		cin >> val;
		tail->_next = new ListNode(val);
		tail = tail->_next;

	}
	return head._next;
}
ListNode* CreateNode(int x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);

	}
	newnode->_next = NULL;
	newnode->_val = x;
	return newnode;
}
int main()
{
	ListNode* list = CreateList(5);
	return 0;
}

 看到C++里写的链表就会发现实现一个链表其实是很快的。

优势四

new失败之后是直接抛异常的,malloc失败之后是会进行检查的,所以new不需要进行失败检查。

operator newoperator delete函数

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

operator new

它是可以直接调用的,但是我们一般不直接的调用,new失败之后是直接抛异常的,抛异常的整个过程是在operator new上,我们调用new的时候其实是先调用operator new 然后再去调用相应的构造函数,但是operator new的底层其实还是malloc,所以operator new其实就是对malloc的封装,失败之后就抛异常,实现new。

operator是一个对malloc进行封装的函数,但是new是一个操作符,之前说过操作符其实都是在编译的时候就转化为相应的指令了,和函数不同,函数实在我们运行的时候进行的调用,所以两者之前是存在偏差的,new转化为指令是会去调用operator new 函数 然后再去调用构造函数,这两个操作都是new的底层。

operator new[]

其实这里也是一样的道理

我们new [] 之后是回去调用operator new []  然后operator new[] 再去调用operator new 和n次构造函数。

operator delete

这里需要思考的一个问题其实就是delete之后先去调用析构函数还是先去调用operator delete的问题,我们可以来看看下面的这段代码,这是一个stack的析构和构造函数。

class Stack
{
public:
	Stack()
		:_top(0)
		, _capacity(0)
		, _a(new int[4])
	{

	}
	~Stack()
	{
		delete[] _a;
		_capacity = _top = 0;
	}
private:
	int* _a;
	size_t _top;
	size_t _capacity;
};

如果先去释放内存的化,这里的一个问题就是内存泄漏,所以我们要做的先去调用析构函数,然后再去调用我们的operator delete

/*
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)

可以看到其实free也是一个宏。

通过上述两个全局函数的实现知道, operator new 实际也是通过 malloc 来申请空间 ,如果
malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常。 operator delete 最终是通过 free 来释放空间的

newdelete的实现原理

这里再和大家分享一个new对于内置类型和自定义类型的处理,和有没有析构函数的处理是不是一样的,首先先写一个类。然后对不同的情况进行处理。

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main()
{
	int* ptr = new int[10];
	delete ptr;
	return 0;
}

先来看看这种情况,我们没有配对的使用,但是最后的运行的情况是合法的,也没有进行报错,是因为这是一个内置类型,来看看汇编代码。

 可以看到的是我们也是new40个字节大小出来,再来看看自定义的的类型是个怎么样子的。

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main()
{
	int* ptr = new int[10];
	delete ptr;

	A* ptr1 = new A[10];
	delete ptr1;
	return 0;
}

再来看看这个编译是没有问题,但是如果我们进行运行的时候就是会报错。

有人会说这里只是调用了一次析构函数,其实不是的,是因为我们释放内存的时候,没有从最开始的指向开始,而是从中间一段地方开始的,这就和我们银行的分期付款是差不多的,我们可以看啊看汇编代码。

 可以看到是44的字节大小,但是我们自定义类型的A其实只要四个字节的存储大小,所以里面的原因就是我们再前面多开一个字节的大小来进行存储。

所以才会这样,但是还有一个奇怪的现象就是如果我们把析构函数去掉的化就会变成不会报错。

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	/*~A()
	{
		cout << "~A()" << endl;
	}*/
private:
	int _a;
};
int main()
{
	int* ptr = new int[10];
	delete ptr;

	A* ptr1 = new A[10];
	delete ptr1;
	return 0;
}

 这样写也不对,但是也不会报错,所以得出一个结论。

new和delete要配对使用,要不然结果是不确定的

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

 

 定位new

我不算再这个部分来写,让大家来可以简单的了解一下定位new后面会学一些池化技术,这个时候就是需要用的时候,定位new其实是手动的去调用自定义类型的构造函数,构造函数是不能调用的,因为再定义的时候自动调用,但是定位new可以,所以定位new我们现在就可以理解为它是对一块已有的空间调用构造函数。 

可以来看看它是怎么使用的,这块大家先了解一下就OK了

class A
{
public:
	A(int n )
		:_a(n)
	{
		
	}
	/*~A()
	{
		cout << "~A()" << endl;
	}*/
private:
	int _a;
};

int main()
{
	A* pa = (A*)malloc(sizeof(A));
	new(pa)A(1);
	return 0;
}

malloc/freenew/delete的区别

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

内存泄漏

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
void MemoryLeaks()
{
   // 1.内存申请了忘记释放
  int* p1 = (int*)malloc(sizeof(int));
  int* p2 = new int;
  
  // 2.异常安全问题
  int* p3 = new int[10];
  
  Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
  
  delete[] p3;
}

内存泄漏其实就是对一块已经不再使用的空间没有进行释放。

内存泄漏是要程序员进行控制的,内存泄漏是不会报错的。

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

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

相关文章

腾讯云4核8G服务器多少钱?

腾讯云4核8G服务器多少钱&#xff1f;轻量应用服务器4核8G12M带宽一年446元、646元15个月&#xff0c;云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元&#xff0c;5年6490.44元&#xff0c;标准型SA2服务器1444.8元一年&#xff0c;在txy.wiki可以查询详细配置和精准报价…

Apache 神禹(shenyu)源码阅读(三)——被网关路由的后端服务 Client 向 Admin 注册的数据传输(Client端)

前言 在真正测试 Divide 插件时&#xff0c;想要知道后端服务&#xff08;以下称为 Client&#xff09;是如何将自己的信息注册到管理台&#xff08;以下称为 Client&#xff09;。这里后端服务用的是 shenyu 自带的 http 的例子&#xff0c;项目名字为 shenyu-examples-http。…

QlikSense财务聚合函数:IRR/NPV/XIRR/XNPV

IRR - 脚本函数 IRR() 函数用于返回聚合内部回报率&#xff0c;以揭示迭代于 group by 子句定义的大量记录上的表达式的数值表示的现金流系列。 这些现金流不必是均值&#xff0c;因为它们可用于年金。但是&#xff0c;现金流必须定期出现&#xff0c;例如每月或每年。内部收…

BUGKU-WEB 你必须让他停下

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 图片会消失,那应该是使用了js来控制根据提示,那就是要停止js才会看到flag (也就是要抓包,不要陷入停止js的思维) 相关工具 F12大法Burp Suit抓包工具 解题步骤 出现图片的时候,源码中确实出现…

CISA知识点

审计流程21%&#xff1b;运营和业务恢复23%&#xff1b;保护资产27%&#xff1b;IT治理17%&#xff1b;开发12%。 领域1-信息系统审计流程 规划-现场工作-报告 &#xff08;1&#xff09;审计规划 了解业务使命、目标、目的和流程 找到相关规定 实施风险分析&#xff08;…

权限提升:利用Linux错配提权

目录 Linux权限基础 Linux用户权限 Linux文件权限 特殊的Linux文件权限 Linux本机信息收集 Linux错配提权 crontab计划任务提权 SUID提权 Linux权限基础 Linux用户权限 在Linux中&#xff0c;根据权限的不同&#xff0c;大致可以分为三种&#xff1a;超级用户&#x…

linux内核原理--用户态线性地址空间,mmap,malloc,缺页异常

1.概述 前面我们介绍了内核态线性地址空间划分&#xff0c;及在内核态运行时&#xff0c;如何利用伙伴系统完成连续可用物理页框申请和释放。如何利用小块内存分配器实现高效的动态内存分配和释放。如何利用vmalloc&#xff0c;vfree完成线性地址连续但物理地址不连续的多个页框…

山西电力市场日前价格预测【2024-02-14】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-02-14&#xff09;山西电力市场全天平均日前电价为203.58元/MWh。其中&#xff0c;最高日前电价为348.00元/MWh&#xff0c;预计出现在19:00。最低日前电价为0.00元/MWh&#xff0c;预计出…

DarkSide针对VMware EXSI系统进行加密

前言 最近黑客组织利用DarkSide勒索病毒对Colonial Pipeline 发起勒索攻击&#xff0c;国内外各大安全厂商和安全媒体也都有相关报道&#xff0c;DarkSide勒索软件是从2020年8月出现&#xff0c;并以(RAAS)勒索即服务的商业模式进行运作&#xff0c;此勒索病毒不仅可以部署基于…

片上网络NoC(6)——路由算法

目录 一、概述 二、路由算法的类型 三、避免死锁 四、实现 4.1 源路由实现 4.2 基于节点查找表的路由实现 4.3 组合电路实现 五、总结 一、概述 路由算法&#xff08;routing algorithm&#xff09;&#xff0c;即决定数据包在网络拓扑中从起点到终点路径的算法。路由算…

作为国产大模型之光的智谱AI,究竟推出了多少模型?一篇文章带你详细了解!

虽然OpenAI发布了一系列基于GPT模型的产品&#xff0c;在不同领域取得了很高的成就。但是作为LLM领域绝对的领头羊&#xff0c;OpenAI没有按照其最初的Open初衷行事。无论是ChatGPT早期采用的GPT3&#xff0c;还是后来推出的GPT3.5和GPT4模型&#xff0c;OpenAI都因为担心被滥用…

人工智能时代

一、人工智能发展历史:从概念到现实 人工智能(Artificial Intelligence,简称AI)是计算机科学领域中一门旨在构建能够执行人类智能任务的系统的分支。其发展历程充满曲折,从概念的提出到如今的广泛应用,是技术、理论和实践相互交织的产物。 1. 起源(20世纪中期) 人工智…

OWASP TOP10

OWASP TOP10 OWASP网址&#xff1a;http://ww.owasp.org.cn A01&#xff1a;失效的访问控制 例如&#xff1a;越权漏洞 案例1&#xff1a; 正常&#xff1a;每个人登录教务系统&#xff0c;只能查询自己的成绩信息 漏洞&#xff1a;张三登录后可以查看自己的成绩 例如&…

WebSocket 通信流程,注解和Spring实现WebSocket ,实战多人聊天室系统

一、前言 实现即时通信常见的有四种方式-分别是&#xff1a;轮询、长轮询(comet)、长连接(SSE)、WebSocket。 ①短轮询 很多网站为了实现推送技术&#xff0c;所用的技术都是轮询。轮询是在特定的的时间间隔&#xff08;如每1秒&#xff09;&#xff0c;由客户端浏览器对服务…

JavaScript中的querySelector()方法是什么,它是如何工作的?

在JavaScript中&#xff0c;有时您需要访问HTML元素。querySelector方法是一个Web API&#xff0c;它选择与传入的指定CSS选择器匹配的第一个元素。 但是&#xff0c;更详细地说&#xff0c;这是如何工作的呢&#xff1f;在本文中&#xff0c;我们将看一些如何使用querySelect…

2022年12月电子学会青少年软件编程 中小学生Python编程等级考试二级真题解析(判断题)

2022年12月Python编程等级考试二级真题解析 判断题&#xff08;共10题&#xff0c;每题2分&#xff0c;共20分&#xff09; 26、字典的元素可以通过键来访问&#xff0c;也可以通过索引(下标)来访问 答案&#xff1a;错 考点分析&#xff1a;考查字典相关知识&#xff0c;字…

Java与JavaScript的区别与联系

Java是目前编程领域使用非常广泛的编程语言&#xff0c;相较于JavaScript&#xff0c;Java更被人们熟知。很多Java程序员想学门脚本语言&#xff0c;一看JavaScript和Java这么像&#xff0c;很有亲切感&#xff0c;那干脆就学它了&#xff0c;这也间接的帮助了JavaScript的发展…

vivado Shift Registers、Dynamic Shift Registers

移位寄存器是一个触发器链&#xff0c;允许数据在固定&#xff08;静态&#xff09;数字上传播延迟阶段。相反&#xff0c;在动态移位寄存器中&#xff0c;传播链的长度在电路操作期间动态变化。从“coding”下载编码示例文件示例。 静态移位寄存器元件 静态移位寄存器通常包…

收藏:不错的讲座《拆解成功领导者的三重底层思维逻辑》

在B 站看到个不错的讲座《拆解成功领导者的三重底层思维逻辑》&#xff0c;地址&#xff1a;第145期-拆解成功领导者的三重底层思维逻辑_哔哩哔哩_bilibili 演讲内容文章摘要在这里&#xff1a;《直播精华 | 拆解成功领导者的思维逻辑》&#xff08;直播精华 | 拆解成功领导者的…

揭秘 2024 春晚刘谦魔术——代码还原

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、魔术大概流程 二、代码实现各个步骤 2.1 partition&#xff08;对半撕牌&#xff09; 2.2 bottom&#xff08;将 n 张牌置底…