探索C嘎嘎:内存管理

忘梓. 2024-10-22 10:05:02 阅读 78

前言:

  小编在前几篇博客结束了类和对象的讲解部分,各位读者朋友一定要掌握这些内容,因为类和对象的知识点是我们以后学习的基础,掌握好它我们以后学习起来才会变的轻松许多,废话小编就不多说了,下面开始今天内容的讲解,代码时间到。


目录

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

1.1.C/C++中程序区域内存划分

1.2.一个相关题目的讲解

 2.C语言中内存管理的方式

2.1.malloc函数

2.2.calloc函数

2.3.realloc函数

2.4.free函数

3.C++中内存管理的方式

3.1.new操作符

3.1.1.单纯动态内存分配一个空间

3.1.2.分配一个空间并且初始化

3.1.3.想要分配很多空间

3.1.4.不仅想要分配很多空间,并且还想要初始化

3.2.delete操作符

1.释放一个空间

2.释放一块空间

3.3.new/delete操作自定义类型

 3.3.1.malloc/free函数

 3.3.2.new/delete操作符

4.operator new与operator delete函数

5.new和delete的实现原理

5.1.内置类型

5.2.自定义类型

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


正文:

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

1.1.C/C++中程序区域内存划分

  在进行讲解动态内存管理之前,小编先给各位读者朋友讲述一下再C/C++中的程序区域内存划分,可能很多读者朋友在学习C语言的时候就知道了这些内存区域,但难免可能会忘掉一部分(就比如小编自己),所以小编先给读者朋友科普一下区域内存的划分,如下图所示:

  为了让各位读者朋友了解的更加深刻一些,小编也给出了文字进行补充说明:

  1.栈又叫堆栈(和数据结构的堆栈不同)——放置的有非静态局部变量/函数参数/返回值等等,栈是向下增长的。

  2.内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。

  3.堆用于程序运行动态内存分配,堆是可以向上增长的。

  4.数据段:存储全局数据和静态数据。

  5.代码段:可执行的代码/只读常量。

1.2.一个相关题目的讲解

  我们光知道上面那个知识点,没有实践的话就有点纸上谈兵的意思了,所以小编给各位读者朋友出了一道题,来考验·一下自己对这部分的内容是否掌握:

<code>int a = 1;

static int b = 1;

void wang()

{

static int c = 1;

int d = 1;

int arr1[10] = { 1,2,3 };

char arr2[] = "abcd";

const char* pArr1 = "abcd";

int* pr1 = (int*)malloc(sizeof(int) * 4);

int* pr2 = (int*)calloc(4, sizeof(int));

int* pr3 = (int*)realloc(pr2,sizeof(int) * 4);

free(pr1);

free(pr3);

}

  老规矩,读者朋友先自己看一下这个题目,下面小编开始对这个题目进行讲解,首先,我们先看a的位置,此时a放在全局里面,所以一眼便是全局变量,全局变量都放在静态区中,所以这个选C;之后在看b,虽然前面b是用static修饰过的,把它变成了静态变量,但是b本身就是静态变量,所以还是选择C;之后在看c,c虽然放在函数体里面,但是它经过了static修饰,已经升级为静态变量,所以此时c已经在静态区了,所以还是选C;之后在看d,此时d放在函数域里面,一般函数里面的变量都放置在栈中,所以选择A;之后在看arr1,arr1是一个数组,但是本身还是在函数中,所以此时它还是建立在栈中,选A;我们在看arr2,arr2也是一个数组,只不过存放的是字符串常量,所以还是建立在栈中,选A;之后在往下看,此时是对arr2进行解引用,解引用后取到的应该是第一个字符a,还是存放在栈中,选A;之后我们继续往下看,此时是pArr1依旧是一个数组,所以它还是在栈上建立的,选择A;但是此时的解引用和上面就不一样了,因为它是一个被const修饰过的数组,所以此时被解引用的对象具有常量性,所以应该放在常量区中,所以此时这个选择D;之后在看下面,pr2是一个被动态开辟出来的数组,但是还是一个数组本质上,是在栈上建立的,选择A;但是它解引用后的对象是动态开辟出来的,所以应该放在堆上,选择B。以上便就是这个题目的解题流程,各位读者朋友一定要去好好的掌握这方面的知识,这对于我们以后的学习有很大的帮助,下面我们将要正式进入动态内存开辟的内容!

 2.C语言中内存管理的方式

  在正式讲述C++的动态内存管理的方式之前,小编先带各位读者朋友回顾一下C语言阶段我们曾学习过的动态内存管理,这里小编先自我批评一下,我在C语言阶段学习了很多的知识点,但是因为我在大一时候的懒惰,使得一些比较不错的博客我没有写下去,其实就包括动态内存管理,所以小编决定在C++的学习过程中,每学习完一个知识点,都要生成一篇博客,以此来加强我的知识的的运用能力,下面小编不多废话,开始进入各种动态函数讲解环节

2.1.malloc函数

  

  上图便就是小编在一个网站上找到的malloc函数的讲解,这里小编简单讲解一下,malloc函数的功能就是开辟出一块void*的空间,括号里面放置的是你想开辟空间的大小,再看一眼返回值,返回值是void*,所以如果我们想开辟整形的空间,那就必须用到强制类型转换了,即(类型),什么类型都可以,小到字节,大到类都是可以开辟出来的,下面小编就拿·开辟四个大小为int的空间进行举例:

<code>int* arr = (int*)malloc(sizeof(int) * 4);

  上面便就是malloc函数,不过经过malloc函数动态开辟出来的变量,是不会进行初始化操作的,下面小编就介绍一个不仅帮你开辟好,还帮你初始化的函数:calloc。

2.2.calloc函数

   上图是对于calloc函数的讲解,它其实和malloc函数的功能是差不多的,只不过calloc函数比malloc函数多了会初始化动态内存开辟的变量,它的使用方法也是蛮简单的,上面的讲解也说了,它和malloc函数是相同的返回值,所以此时我们还需要用到强制类型转换,下面小编展示一下对于calloc使用的代码:

<code>int* arr1 = (int*)calloc(4, sizeof(int));

  上面便就是这个函数的使用方法,calloc函数其实使用的频率没有上面的malloc函数以及接下来要将的calloc函数频繁,但是各位读者朋友也需要直到大致的用法,下面我们继续进入下一个函数的讲解。

2.3.realloc函数

   这个函数相比各位读者朋友都不陌生了,这个函数小编常常在数据结构用,就比如扩容操作就需要使用它,所以它的功能其实就是执行扩容操作的,当我们动态开辟后的空间不够用,那就可以接着扩容,因为使用的次数很频繁,我就不多讲了,下面小编就给各位展示一下代码使用:

<code>int* arr2 = (int*)realloc(arr1,sizeof(int) * 4);

2.4.free函数

  最后,我们在使用完动态内存分配的空间以后,我们还需要自己去进行回收操作,不然可能会出现内存泄露的问题,可能目前我们无法清楚内存泄露的危害,但当我们写一个大型项目的时候,往往一个小的点就可能会使整个项目崩盘,所以作为程序员的我们,一定要培养好回收空间的好习惯,这里就需要用到free函数,这个函数的使用方法很简单,我们仅需在括号里放入我们想要释放的空间即可,代码如下所示:

<code>free(arr);

free(arr1);

free(arr2);

arr = arr1 = arr2 = NULL; //释放完空间一定要让它们指向空,避免野指针的出现

  以上便就是C语言中我们在动态内存管理的时候常常使用到的函数,各位读者朋友要清楚它们的功能,在以后我们也是会使用到的,下面小编将要进行C++阶段动态内存管理的方式。

3.C++中内存管理的方式

  讲述完了C语言的内存管理方式了,下面我们就要进入C++的内存管理了,这个才算是这篇文章的重点,因为毕竟我们目前的阶段是C++而不是C语言了,不过C语言的函数C++还是可以正常使用的,但有些地方就有点无能为了,而且使用方法比较麻烦,于是C++推出了一套自己的内存管理方式,下面进入C++内存管理方式的讲解。

3.1.new操作符

  请好好注意小编写的这一个标题,此时C++给我们提供了一个操作符,而不是一个函数,这个操作符被命名为new,相较于C语言中动态内存管理的函数这么多,C++仅凭一个new便可以实现动态内存分配,这更好的体现了C++语言的简洁美,废话不多说,下面小编介绍一下new操作符的使用方式。

  首先,new操作符可以分为四种操作方式,小编就用自己的语言叙述了,分别是:

3.1.1.单纯动态内存分配一个空间

  正如标题所言,new操作符可能会类似malloc函数一样,仅仅开辟空间,我们也不初始化,仅需开辟一个空间(malloc函数是可以去控制个数的),这就是new操作符的基本使用,它用起来很简单,左边就是某一个类型的指针加名字,右边就是new + 类型,如下面代码所示:

<code>int* p1 = new int;

float* p2 = new float;

char* p3 = new char;

3.1.2.分配一个空间并且初始化

  这个操作是延续上面的操作来的,我们仅需在类型后面加个括号(),括号里面放置我们想要初始化的内容即可,如下所示:

int* p1 = new int(1);

float* p2 = new float(1.2);

char* p3 = new char('a');

3.1.3.想要分配很多空间

  这个操作也是延续第一个操作进行的,此时我们仅需在类型后面加上[]即可,此时在[]里面放置我们想要设置的个数即可,如下所示:

int* p1 = new int[12];

float* p2 = new float[12];

char* p3 = new char[12];

3.1.4.不仅想要分配很多空间,并且还想要初始化

  这个操作我们是延续3.1.3来的,此时我们仅需在[]后面加上{}即可,我们继续在里面放置好我们想要的数即可,如果数多了,会直接在运行的时候弹出警告,如果个数不够,譬如整形,会自动把剩下的元素初始化为0,这就类似于我们给数组进行初始化操作,下面给上代码:

int* p1 = new int[12] {1,2,3,4,5,6,7,8};

float* p2 = new float[12] {1.1,1.2,1.3};

char* p3 = new char[12] {'a','b','c','d','e','f'};

  以上便就是new操作符的使用,此时我们从这知道new操作符的用法还是蛮有趣的相较于C语言那几个函数,就比如这个我们可以自己初始化的操作,上面那仨除了第二个可以算是初始化,其他事做不到的,当然对于内置类型,其实两者区别没有很大,他们区别最大的还是类类型,等会各位读者朋友就知道了,有开辟就有释放,下面我们进入下一个操作符的详解。

3.2.delete操作符

  当然,C++中用于释放开辟空间的也是一个操作符,它的名字我们也很熟悉,本身就有删除的意思,对于delete操作符,同样也有两种使用方式,下面小编不多说废话直接进入讲解使用环节:

1.释放一个空间

  当我们释放一个空间的时候,我们继续delete+开辟空间的名字即可,如下所示:

delete arr1;

2.释放一块空间

  当我们开辟很多空间的时候,此时我们仅需在delete后面加一个[],标志着开辟了不少空间,然后加开辟空间的名字即可,如下所示:

delete[] arr1;

3.3.new/delete操作自定义类型

  对于内置类型,小编前面就说了,对于C语言和C++的动态内存分配,在内置类型差距不大,它们最大的差距就是在于类类型,类是C++一个特别重要的内容,所以如果哪个对于类方便,哪一个就是最好用的,下面小编给出一段类的代码,让new,delete和malloc,free二者分别创建空间,看看他们的运行情况:

 3.3.1.malloc/free函数

class wang

{

public:

wang()

{

_year = 12;

cout << "wang()" << endl;

}

~wang()

{

cout << "~wang" << endl;

}

private:

int _year;

};

int main()

{

wang* s1 = (wang*)malloc(sizeof(wang));

cout << "/" << endl;

free(s1);

cout << "/" << endl;

return 0;

}

  下面我们看一下运行结果:

   我们在对比一下new/delete是如何去完成这两个函数的。

 3.3.2.new/delete操作符

<code>class wang

{

public:

wang()

{

_year = 12;

cout << "wang()" << endl;

}

~wang()

{

cout << "~wang" << endl;

}

private:

int _year;

};

int main()

{

wang* s1 = new wang;

cout << "/" << endl;

delete s1;

cout << "/" << endl;

return 0;

}

  下面来看一下它们的运行结果:

   此刻C++的内存管理和C语言的内存管理对于类类型的变量差距就体现出来了,通过两图,我们可以清晰的看出,当我们使用malloc/free函数的时候,这两个函数真就仅仅的去开辟空间;当我们使用new/delete操作符的时候,new操作符在被动态开辟出来后,自动帮我们去调用了构造函数,当我们delete开辟好的空间以后,自动帮我们调用了析构函数,这边就是它们在处理类类型对象的差距,此时我们通过运行图便可以知道我们以后到底去使用什么去进行内存管理,那当然是——new和delete,这两个操作符可以让我们出错的可能性小一点,这就体现出了C++的严谨性。这便是我想告诉读者朋友们的,当然,我们不光要知道new/delete怎么用,还要去了解一下它们的底层是如何实现的,下面小编就简单给大家讲述一下两个重要的函数。系好安全带,出发喽!

4.operator new与operator delete函数

  可能很多读者朋友看到标题会很奇怪,这new和delete不是操作符吗?怎么又蹦出来个这俩函数,这俩函数和操作符又有什么联系,不要着急,小编给各位读者朋友认真解释的。首先,new和delete是用户进行动态内存申请和释放的操作符,而operator new和operator delete是系统提供的全局函数,new在底层是通过调用operator new来实现开辟空间,delete在底层是通过调用operator delete来实现释放空间,所以这两个函数是属于一个底层的函数,均是为了实现操作符而生的,下面小编从代码段中截取了它们的代码供读者朋友阅读:

<code>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)

  上面便就是operator new和operator delete的相关代码,其中可能有很多读者朋友会认为根本读不懂这段代码,这是正常的,小编也是读不懂,但是我们还能看到几个熟悉的函数,就比如malloc,free函数,从而我们可以知道operator new其实也是用malloc函数来进行申请空间的,如果申请成功直接就返回了,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。(异常这个概念小编以后会说,但是不是现在,现在小编也不清楚这个,但是以后小编学习完以后一定会回来进行补充说明的)。而operator delete的底层则是free实现的,各位读者朋友要知道这些事情,一日积累一点,日积月累,我们就可以成为一个非常强大的程序员,下面我们进入下一部分的讲解!

5.new和delete的实现原理

5.1.内置类型

  如果申请的是内置类型的空间,new和malloc,free和delete基本上是类似的,但是不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是一块连续的空间,并且如果new失败了是会抛异常的,malloc失败会传NULL。但是小编还是推荐各位读者朋友使用new和delete,毕竟我们学的是C++,所以函数最好也是C++的。

5.2.自定义类型

●new的原理

  1.调用operator new函数申请空间。2.在申请空间的同时调用构造函数,完成对象的构造。

●delete的原理

  1.调用operator delete函数释放空间。2.在释放空间的同时调用析构函数,完成对象的析构。

●new[T]的原理

  1.调用operator new[]函数申请空间,而operator new[]实则是调用了T次operator new来进行申请空间。2.在申请的空间调用了T次构造函数

●delete[T]的原理

  1.调用了operator delete[]函数释放空间。2.在释放空间的过程中调用了T次析构函数。

  以上便就是这两个操作符的实现原理,各位了解就可以,重点是知道如何使用以及它们的底层是如何去实现的。

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

  其实区别小编在前面就多次提到了,但是我还是系统性的总结一下这俩的区别吧。

  malloc/free和new/delete的共同点都是从堆上去建立空间,并且都需要自己去手动释放空间,不然有内存泄露的风险,但有以下的不同点:

  1.malloc和free是俩函数,new/delete是俩操作符。

  2.malloc申请的空间不会初始化,new是可以进行初始化操作的(上面讲过)。

  3.malloc申请空间时,需要自己手动计算一下要开辟空间的大小并传递,new只需在后面加上空间的类型即可,如果想要申请多个的话,直接[]里面加上个数即可,灰常的方便~

  4.malloc的返回值是void*,在使用的时候一定要强转,new不需要,因为new后面会跟上开辟空间的类型。

  5.malloc申请空间失败的时候,会返回空,所以必须去判空,而new申请空间失败,不要判空,只需自己去捕获异常即可。

  6.申请自定义类型的时候,malloc/free只会开辟空间,不会调用析构函数和构造函数,而new在申请空间的时候会自动调用构造函数完成初始化,在delete销毁的时候自动调用析构函数去析构对象,完成空间中资源的清理。

  以上便就是区别,各位读者朋友要牢记~

7.总结

  以上便就是今天小编要讲述的内容,各位读者朋友一定要知道C++是如何进行内存分配的,明白new和delete的用法,小编其实还有一个点没讲,那就是定位new,但我感觉这部分不太重要,最主要的是我都不太明白,我就不自己尝试讲去祸害别人了,如果文章有错误,请在评论区指出,我定会及时的回复,那么,我们下一篇文章见啦!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。