链表并不能解决堆中所有问题,还需要内存池

堆和栈与静态内存分配不一样,虽然都在进程地址空间内,但是它们并没有提前就分配好空间,而是在程序运行的时候进行分配,不过栈是操作系统自动分配和释放,而堆是由开发人员根据具体需求进行分配,释放,这就是我们今天的主题,内存的动态分配。

相信学过C语言直系亲属的C++、objective-C等语言的朋友们知道,在早期的时候,它们都需要手动的通过malloc函数进行分配,然后又需要手动的进行释放(oc中是alloc和dealloc),不过这样开发效率很低,往往会因为引用和内存忘记释放出现各种奇葩问题。我记得当时有人说过,用手动操作内存,其编码过程中消耗的时间要比使用ARC机制的编程语言多一倍左右。后来,随着各种新的编程语言的出现,其中又增加各种新的思维方式,有了ARC等垃圾自动回收机制后,在编码过程中,编程人员不用再为内存的释放而操心,大大加快了编程速度,降低了编程的门槛。不过有一点要注意,C++和C至今还是没有官方的ARC机制,不排除有大佬自己实现。

不过就我从以前学习objective-C时学习的MRC和后来使用的ARC来说,编码速度确实使用ARC来的快,但是编译和软件的使用效率上看,ARC更容易出现卡顿等现象,说简单点,就是没有以前那么顺畅。如果多翻一翻GitHub上的一些大佬写的项目,就会发现这些项目中,在比较关键的地方仍然使用MRC机制,手动的管理内存的引用和释放(Java很早就出现垃圾自动回收机制了)。我个人觉得培训班之所以能够批量生产编码员,就是因为现在语言不再让程序员需要去考虑内存管理等问题,全部交给计算机去处理了。

我们上面说了很多MRC和ARC等机制,实际上万变不离其宗,终究是对内存进行动态的分配和释放,所以我们先来说内存的分配和释放。我们在说数组的时候遇到过这样的问题,本来我们的数组要放是个元素,但是后来需要向里面增加其他几个元素,这时候,我们前面使用的基本数组类型的长度是不能变的,所以会出现不能储存的问题,这时候怎么办呢,于是就需要我们自己取申请一个可变长的的数组。静态分配?不行,早早就给把内存空间分配好了。栈?这也不行,我们在定义基本数据类型变量时已经告诉数组长度,系统也给分配好了,还想变,不可能。那么我我们就只剩下堆了,堆是我们编程人员可以控制的一片区域空间,所以我想申请多大空间就可以申请多大空间,不过一般来说,在分配内存的时候,我们不需要告诉计算机我们需要多大的内存,毕竟在不同的操作系统上,一个数据类型的长度是不同的。需要注意的是,在我们分配了内存后,就必须要将其释放掉,否则数据会一直存在,直到程序结束,如果不释放,内存就会渐渐变满,是不是就出现问题了,所以在内存操作的时候一定要记住一个成语:“有始有终”。

通过上面的描述,我们是不是就可以看到这三种内存分配模式的效率问题了。静态内存是在程序启动时就已经分配好了,程序在运行过程中直接拿来用。栈是需要等待程序运行时自动分配和释放。但是堆的话,既不能在程序启动时分配空间,又不能在程序运行时提前分配空间,只能等待在需要的时候增加内存或减少内存。这样看来,静态内存分配效率最快,然后是栈,最后才是堆。

链表

当然,对只是内存的分配,函数才是我们操作内存的主要手段,在C语言中,能够动态内存分配的函数有malloc(常用),Calloc(分配连续的一块内存)、realloc(重新分配内存大小)、free(释放上面所申请的内存),那么这些函数是怎么实现在堆上的内存申请和释放呢,首先,堆的内存并不是连续不断的,它在申请的时候会向系统申请一块内存空间,当这片内存空间用完后,它会向系统继续申请另一块空间,这时候系统会从低地址向高地址遍历,如果出现大小合适的空闲空间,那么就会通过我们前面所说的指针进行连接,这种方式是不是和链表一个道理,所以在学习C语言的时候,总是会提倡要求了解链表的概念和实现方式。当然,这种方式是有弊端的,当链表中指针被破坏时,堆就会出现问题,被分开的内存空间可能会被其他程序越界读取和使用,而且长期对内存进行遍历,数据小时还行,当数据达到一定规模后,每次进行遍历,都需要消耗大量时间,所以就有了新的理论和观点,就是我们说的内存池,内存池主要讲的并不是内存的申请和分配问题,而是对自己内存的管理,不过这个内存池管理的原理比较复杂。我们前面说的ARC和MRC实际上就是对内存池的管理,当申请内存空间时,在池子中对这个对象进行引用加一,这片内存空间没有任何引用时,才正式释放掉,这就是我们常说的“内存垃圾回收机制”。

你可能感兴趣的:(链表并不能解决堆中所有问题,还需要内存池)