5

[ C++ ] 一篇带你了解C++中动态内存管理

 2 years ago
source link: https://blog.51cto.com/xingyuli/5509008
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在我们日常写代码的过程中,我们对内存空间的需求有时候在程序运行的时候才能知道,这时候我们就需要使用动态开辟内存的方法。

1、C/C++程序的内存开辟

首先我们先了解一下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);
}
[ C++ ] 一篇带你了解C++中动态内存管理_new_02

1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。

3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

这幅图中,我们可以发现普通的局部变量是在栈上分配空间的,在栈区中创建的变量出了作用域去就会自动销毁。但是被static修饰的变量是存放在数据段(静态区),在数据段上创建的变量直到程序结束才销毁,所以数据段上的数据生命周期变长了。

2.C语言中动态内存管理方式:malloc/calloc/realloc/free

在C语言中,我们经常会用到malloc,calloc和realloc来进行动态的开辟内存;同时,C语言还提供了一个函数free,专门用来做动态内存的释放和回收。其中他们三个的区别也是我们需要特别所强调区别的。

2.1malloc、calloc、realloc区别?

malloc函数是向内存申请一块连续可用的空间,并返回指向这块空间的指针。

calloc与malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为0。

realloc函数可以做到对动态开辟内存大小的调整。

我们通过这三个函数的定义也可以进行功能的区分:

void Test ()
{
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 );
}

3.C++内存管理方式

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

3.1 new/delete操作内置类型

int main()
{
// 动态申请一个int类型的空间
int* ptr1 = new int;

// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);

// 动态申请3个int类型的空间(数组)
int* ptr3 = new int[3];

// 动态申请3个int类型的空间,初始化第一个空间值为1
int* ptr4 = new int[3]{ 1 };

delete ptr1;
delete ptr2;
delete[] ptr3;
delete[] ptr4;

return 0;
}

  我们首先通过画图分析进行剖析代码:

[ C++ ] 一篇带你了解C++中动态内存管理_c++_06

我们在监视窗口看看这3个变量

[ C++ ] 一篇带你了解C++中动态内存管理_c++_08
[ C++ ] 一篇带你了解C++中动态内存管理_new_10

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],要匹配起来使用。

3.2 new和delete操作自定义类型

class A {
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;

return 0;
}

在这段代码中,p1是我们使用malloc开辟的,p2是通过new来开辟的。我们编译运行这段代码。

[ C++ ] 一篇带你了解C++中动态内存管理_动态内存管理_13

发现输出了这两句,那这两句是谁调用的呢?我们通过调试逐语句来分析这个过程

[ C++ ] 一篇带你了解C++中动态内存管理_c++_15

内置类型区别

注意:在申请自定义类型的空间时,new会自动调用构造函数,delete时会调用析构函数,而malloc和free不会。

3.3new和malloc处理失败

int main()
{
void* p0 = malloc(1024 * 1024 * 1024);
cout << p0 << endl;

//malloc失败,返回空指针
void* p1 = malloc(1024 * 1024 * 1024);
cout << p1 << endl;

try
{
//new失败,抛异常
void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}

return 0;
}
[ C++ ] 一篇带你了解C++中动态内存管理_动态内存管理_18

 我们能够发现,malloc失败时会返回空指针,而new失败时,会抛出异常。

4.operator new与operator delete函数

4.1 operator new与operator delete函数

C++标准库还提供了operator new和operator delete函数,但是这两个函数并不是对new和delete的重载,operator new和operator delete是两个库函数。(这里C++大佬设计时这样取名确实很容易混淆)

4.1.1 我们看看operator new库里面的源码

[ C++ ] 一篇带你了解C++中动态内存管理_c++_20
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 new的作用是封装了malloc,如果malloc失败,抛出异常。

4.1.2 operator delete库里面的源码

该函数最终是通过free来释放空间的

[ C++ ] 一篇带你了解C++中动态内存管理_new_23
//operator delete 源码
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)

4.1.3 operator new和operator delete的价值(重点)

class A {
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
//跟malloc功能一样,失败以后抛出异常
A* ps1 = (A*)operator new(sizeof(A));
operator delete(ps1);

A* ps2 = (A*)malloc(sizeof(A));
free(ps2);

A* ps3 = new A;
delete ps3;

return 0;
}

我们使用new的时候,new要开空间,要调用构造函数。new可以转换成call malloc,call 构造函数。但是call malloc 一旦失败,会返回空指针或者错误码。在面向对象的语言中更喜欢使用异常。而operator new相比较malloc的不同就在于如果一旦失败会抛出异常,因此new的底层实现是调用operator new,operator new会调用malloc(如果失败抛出异常),再调用构造函数。

我们通过汇编看一下ps3

[ C++ ] 一篇带你了解C++中动态内存管理_c++_27

operator delete同理。

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

4.2 重载operator new 与 operator delete(了解)

专属的operator new技术,提高效率。应用:内存池

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

// 专属的operator new
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<A>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}

void operator delete(void* p)
{
allocator<A>().deallocate((A*)p, 1);
cout << "memory pool deallocate" << endl;

}

~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
int n = 0;
cin >> n;
for (int i = 0; i < n; ++i)
{
A* ps1 = new A; //operator new + A的构造函数
}

return 0;
}
[ C++ ] 一篇带你了解C++中动态内存管理_new_30

注意:一般情况下不需要对 operator new 和 operator delete进行重载,除非在申请和释放空间时候有某些特殊的需求。比如:在使用new和delete申请和释放空间时,打印一些日志信息,可以简单帮助用户来检测是否存在内存泄漏。

5.new 和 delete 的实现原理

5.1 内置类型

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

5.2 自定义类型

5.2.1 new原理

1、调用operator new函数申请空间

2、再调用构造函数,完成对对象的构造。

5.2.2 delete原理

1、先调用析构函数,完成对对象中资源的清理工作。

2、调用operator delete函数释放对象的空间

5.2.3 new T[N]原理

1、先调用operator new[]函数,在operator new[]中世纪调用operator new函数完成N个对象空间的申请

2、在申请的空间上执行N次构造函数

5.2.4 delete[]原理

1、在释放的对象空间上执行N次析构函数,完成对N个对象中资源的清理

2、调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。

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

6.1malloc/free和new/delete的共同点

都是从堆上申请空间,都需要用户手动释放空间。

6.2malloc/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在释放空间前会调用析构函数完成空间中资源的清理


Recommend

  • 46
    • coffee.pmcaff.com 5 years ago
    • Cache

    一篇文章带你了解APP PUSH推送机制

    写作目的:本文主要讲解关于APP PUSH的流程、机制及相关经验,一是为了方便各位可以针对APP迅速制定PUSH消息推送方案,实现0到1的推送功能搭建,二是可以了解下PUSH流程,对各个环节针对性地提高触达率。一 APP PUSH定义与价值

  • 17

    为什么了解网页布局很重要?网页布局在很大程度上决定了网站的访问者将如何与网页内容进行交互。 这里将介绍一些常见的网页布局形式,例如卡片式、分屏布局、网格布局……一起来看看吧! 卡片式网页布局

  • 17
    • ask.hellobi.com 4 years ago
    • Cache

    一篇文章带你了解CSS3按钮知识

    在实际开发中,按钮的应用是必不可少。使用 CSS 来制作按钮,可以更有新意,更有趣,也可以自定义自己想要的样式。 一、平面样式CSS按钮 平面样式按钮的使用现在非常流行,并且符合无处不...

  • 16

    前言 Hi,大家好,我是码农,星期八,本篇继续带来Go语言并发基础,channel如何使用。 看看Go协程如何配合channel。...

  • 14

    上篇文章,我们介绍了CSS3滤镜效果的模糊效果、设置图像高度、调整图像对比度、向图像添加阴影等知识,这篇文章紧承上篇文章,我们重点介绍下CSS3滤镜效果的将图像转换为灰度、在图像上应用色相旋转、对图像应用不透明度知识。 四、向图...

  • 9

    按钮是最常用的组件之一,有很多小伙伴并没有很全面的认识这个组件,今天我把关于按钮的一些知识和我的一些观点分享给大家。 按钮的作用 在使用按钮之前,你要了解什么是按钮,按钮的作用是什么,什么时候该用...

  • 11

    一篇文章带你深入了解”B端C化”的设计理念 酷家乐用户体验设计 2021-08-22 1 评论...

  • 12

    一篇带你了解Docker背后的原理-51CTO.COM 一篇带你了解Docker背后的原理 作者:浩仔浩仔 2022-02-18 08:54:21 docker轻量,一次封装到处运行,启动快,所以很适合做扩缩容、微服务。

  • 5
    • netsecurity.51cto.com 3 years ago
    • Cache

    一篇带你了解 Thanos Ruler 组件的使用

    一篇带你了解 Thanos Ruler 组件的使用-51CTO.COM 一篇带你了解 Thanos Ruler 组件的使用 作者:阳明 2022-04-13 21:19:56 Thano Ruler 组件是用于评估 Prometheus 的记录规则和报警规则的组件,...

  • 7

    好久不见,甚是想念。喜欢提笔的味道,因为当开始创作的那一刻,心中就拥有了无限的想象。为什么要写广告部分呢,虽然SEO重要,但事实上,它并不适合所有人。其一:并不是所有的网站都适合重点投入SEO,比如产品比较单一的网站、需要快速见到效果的公...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK