当前位置: 首页 > news >正文

learn C++ NO.7——C/C++内存管理

引言

现在是5月30日的正午,图书馆里空空的,也许是大家都在午休,也许是现在37摄氏度的气温。穿着球衣的我已经汗流浃背,今天热火战胜了凯尔特人,闯入决赛。以下克上的勇气也激励着我,在省内垫底的大学中,我不觉得气馁,我要更加努力学习,让自己能够越来越好,以后肯定也会”晋级决赛”。

1.C/C++程序的内存分布

  1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
    创建共享共享内存,做进程间通信。
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段–存储全局数据和静态数据。
  5. 代码段–可执行的代码/只读常量
    在这里插入图片描述

2.变量在内存中存储的位置

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);
}

在这里插入图片描述
下面我按照从左往右从上到下的顺序依次分析上面的题目。globalVar和staticGlobalVar都是定义在全局域中,所以是存储在静态区中的。staticVar虽然是在局部域内定义的,但是它是static修饰的变量所以依旧是存储在静态区中的。localVar和num1都是在局部域内定义的局部变量,都是存储在栈区空间的。上面第一部分比较简单,下面我以画图加分析的来看下面部分。
为什么char2和*char2都在局部域呢?
在这里插入图片描述
pChar3是一个定义在栈区的const char类型指针,它保存的是常量字符串"abcd"的首元素地址。*pChar3是对常量字符串的首元素地址进行解引用操作,访问的是常量区的空间。ptr1是一个在栈区上创建的指针变量,存放的是动态开辟空间首字节地址。解引用访问ptr1访问的是堆区空间。

数组名单独放在sizeof内部表示整个数组,所以是4*10字节。"abcd"其实是隐含了’\0’字符,所以是4+1=5字节。无论什么指针都是4 or 8字节,指针的大小取决于平台,64位平台8字节,32位平台4字节。

3.C语言的动态内存管理

常见的C语言动态内存管理如下:malloc/calloc/realloc/free。具体细节可以移步c语言动态内存管理,查看这里不做赘述。

void Test ()
{int* p1 = (int*) malloc(sizeof(int));free(p1);// 1.malloc/calloc/realloc的区别是什么?int* p2 = (int*)calloc(4, sizeof (int));int* p3 = (int*)realloc(p2, sizeof(int)*10);// 这里需要free(p2)吗?free(p3 );
}

1.malloc就是单纯地开辟堆区空间。calloc是可以指定初始化内容开辟堆区空间。realloc就是动态调整堆区申请的空间。当调整后的空间无法在原空间后扩容,则会将原空间的内容拷贝到新的空间上再申请到连续空间。
在这里插入图片描述

4.C++的动态内存管理

因为C++是兼容C语言的,所以C语言的动态内存管理依旧是可以在C++中使用的。由于C语言的动态管理方式存在缺陷,所以C++也提供了两个操作符来进行动态内存管理。分别是new 和 delete。

4.1.new和delete管理内置类型

int main()
{//cint* p1 = (int*)malloc(sizeof(int));free(p1);//cpp//new后面跟的是类型int* p2 = new int;delete p2;//malloc单纯开辟空间//new支持初始化int* p3 = new int(10);delete p3;int* p4 = (int*)malloc(sizeof(int) * 10);free(p4);int* p5 = new int[10];delete[] p5;//一定要带[]//开辟连续空间也是支持初始化的int* p6 = new int[10]{1,2,3,4};delete[] p6;}

需要注意的是使用malloc申请的内存就用free来释放,使用new申请的内存,就用delete来清理。不可以free来释放new的空间,或者用delete来释放malloc的空间。虽然在内置类型中,可能不会出问题,但是,这样使用是不规范也不正确的。这就好比吃面要用筷子,吃炸鸡要用手套,吃炒饭用勺子。你用free去释放new的内存,就好比用勺子去吃炸鸡,这样是不合适的。在某些场景下,你用free去释放new的内存就会好比用手套去吃火锅,那肯定是不行的。

4.1.new和delete操作自定义类型

struct ListNode
{//c++写法ListNode(int x):_next(nullptr), _val(x){}int _val;struct ListNode* _next;
};//c语言写法
struct ListNode* BuyNode(int x)
{struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));if (newnode == NULL){perror("malloc fail");exit(-1);}newnode->_next = NULL;newnode->_val = x;return newnode;
}int main()
{struct ListNode* n1 = BuyNode(1);struct ListNode* n2 = BuyNode(2);struct ListNode* n3 = BuyNode(3);free(n1);free(n2);free(n3);//struct升级成了类ListNode* nn1 = new ListNode(1);ListNode* nn2 = new ListNode(2);ListNode* nn3 = new ListNode(3);delete nn1;delete nn2;delete nn3;return 0;
}

new可以去调用自定义类型的构造函数来初始化对象,这样写比c语言的写法香多了。

class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}A(const A& aa){cout << "A(const A& aa)" << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};
int main()
{A* p1 = (A*)malloc(sizeof(A) * 4);A* p2 = new A[4]{1,2,3,4};free(p1);delete[] p2;return 0;
}

在这里插入图片描述
从上面样例可以看到,new自定义类型对象的时候,会去调用它的构造函数。当然这里是以整型值初始化自定义类型,会产生隐式类型转换,因为是在同一行编译器自动进行了优化,所以没有调用拷贝构造函数。而malloc只是单纯地开空间。delete自定义类型对象时,会去调用他的析构函数,而free只是单纯释放空间。

4.1.c++和c语言在开辟动态内存时失败处理细节

在C语言中,当使用malloc()函数开辟动态内存时,如果内存不足或者没有足够的连续空间,函数将返回NULL指针,表示内存分配失败。而在C++中,使用new操作符开辟动态内存时,如果内存不足或者没有足够的连续空间,将抛出一个std::bad_alloc异常,表示内存分配失败。

//C语言
int main()
{int* p1 = (int*)malloc(sizeof(int) * 1024);if (p1 == NULL){perror("malloc fail");exit(-1);}return 0;
}//C++
int main()
{int* p1 = nullptr;try{do{p1 = new int[1024];cout << p1 << endl;} while (p1);}catch (const exception& e){cout << e.what() << endl;}return 0;
}

在面向对象的编程语言的编程中,try-catch是一种异常处理机制。在try块中,我们编写可能引发异常的代码。如果在try块中的代码引发了异常,程序会立即跳转到与其对应的catch块。catch块定义了异常处理程序,它会处理try块中引发的异常。这里稍微了解即可,具体细节得等到后面学习后再和大家介绍了。

5.new和delete的原理

5.1.operator new与operator delete函数的介绍

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

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* 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 new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

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_FINALLYreturn;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

operator delete:通过调用free来释放空间,而且operator delete和宏函数free的底层都是调用_free_dbg来进行释放空间的

5.2.operator new、new、operator delete和delete函数的底层实现

首先,通过下面代码的汇编代码来看看operator new、new、operator delete和delete函数究竟是怎么样的吧。

class A
{
public:A(int a = 0,int b = 0): _a(a){cout << "A():" << this << endl;}A(const A& aa){cout << "A(const A& aa)" << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};int main()
{A* p1 = (A*)operator new(sizeof(A));A* p2 = new A(1,1);operator delete(p1);delete p2;return 0;
}

在这里插入图片描述
在这里插入图片描述
通过查看汇编代码我们可以看到,new的会先调用operator new开辟空间,然后在调用构造函数初始化。delete会先调用析构函数进行清理,然后调用operator delete来释放动态内存。当然用new 自定义类型[N] 个对象,会先调用operator new开辟空间,然后在调用N次构造函数初始化。delete[] 会先调用N次析构函数,然后释放动态内存。

在这里插入图片描述

下面通过一个比较复杂的场景来看一下new和delete对于自定义类的的处理

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3){cout << "Stack(size_t capacity = 3)" << endl;_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:DataType* _array;int       _capacity;int       _size;
};int main()
{Stack* p1 = new Stack;delete p1;return 0;
}

在这里插入图片描述

6.定位new

定位new是new关键字的另一种用法,用于给已经分配好的堆区空间进行调用构造函数来初始化对象。

6.1.代码样例演示

class A
{
public:A(int a = 0,int b = 0): _a(a){cout << "A():" << this << endl;}A(const A& aa){cout << "A(const A& aa)" << endl;}~A(){cout << "~A():" << this << endl;}
private:int _a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参p1->~A();//显示调用析构函数free(p1);return 0;
}

在这里插入图片描述

6.2.定位new的应用场景

在一些需要频繁申请堆区内存的程序中,通常需要提前开辟一个内存池,用于提高获取堆区内存的效率。而定位new就能将申请的内存通过调用构造函数来进行初始化。在后面学习的STL的链表中就能遇到这一场景。
在这里插入图片描述

7. malloc/free和new/delete的区别

1、malloc/free是库函数。new和delete是操作符。
2、对于内置类型来说malloc/free和new/delete的区别不是很大,对于自定义类型new/delete分别会去调用构造函数/析构函数。来进行初始化和清理工作。
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
4. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。

8.内存泄漏的概念

8.1.什么是内存泄漏

内存泄漏指的是在程序运行过程中,程序分配了一段内存空间,但在使用完这段内存空间后,没有及时释放掉这段内存,导致这段内存不能被再次使用,从而造成了内存空间的浪费。如果程序中存在内存泄漏问题,并且这种泄漏的情况不断累积,最终可能会导致程序所能使用的内存空间越来越小,甚至导致程序崩溃。内存泄漏是一种常见的编程错误,需要开发人员注意及时释放不再使用的内存空间,避免内存泄漏问题的出现。下面我通过一个样例来看看内存。

int main()
{char* p1 = (char*)malloc(1024 * 1024 * 1024);cout << p1 << endl;return 0;
}

在这里插入图片描述

8.2.内存泄漏的危害

对于客户端,内存泄漏的危害比较的小。对于服务端,内存泄漏的危害极大。因为想游戏服务、电商服务等服务端中,内存泄漏会导致服务程序宕机,进而导致软件业务的事故。所以我们程序员在操作动态内存时,一定要注意在哪里申请了就在对应的位置释放。内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。

http://www.lryc.cn/news/91210.html

相关文章:

  • SDUT数据库原理——第十章作业(参考答案)
  • My Note of Diffusion Models
  • 【P37】JMeter 仅一次控制器(Once Only Controller)
  • cleanmymac要不要下载装机?好不好用
  • DNS风险分析及防护研究(五):常见的DNS威胁与防御(中科三方)
  • 使用geoserver发布shp和tiff数据
  • 谷歌周彦祺:LLM浪潮中的女性科学家多面手丨智源大会嘉宾风采
  • Burp模块
  • sql笔记:SQL SERVER字符串填充(标量值函数创建、标量值函数调用)
  • python使用hTTP方法
  • JavaSE常用API
  • 华为OD机试之模拟商场优惠打折(Java源码)
  • 5月VR大数据:Quest 2下跌超1%,其它变化不大
  • CW32系列模数转换器(ADC)
  • 电动力学专题:电磁场规范不变性与规范自由度
  • max delay的应用场景与常见问题
  • 非阻塞队列
  • 动力电池管理系统(BMS)
  • ChatGPT桌面客户端支持gpt4模型,附使用说明
  • Vivado下时序逻辑模块的仿真
  • ThreadLocal的使用方式
  • 全面理解:C++中的指针和迭代器,以及解引用操作符(*)和箭头操作符(->)的用法
  • Vite 使用学习指南
  • 【算法训练(day6)】双指针模板
  • 免费常用的API接口大全
  • 【HTML】第 2 节 - HTML 标签
  • MATLAB算法实战应用案例精讲-【数模应用】残差检验(附Java、python和MATLAB代码)
  • 初学Qt(Day03)
  • 皮卡丘xss之htmlspecialchars、xss之href输出、xss之js输出
  • ArrayList和LinkedList的区别