15.1.数据结构和内存管理概述
(1)数据结构就是研究数据如何组织(在内存中的具体排布),如何加工的学问。数据结构和内存管理联系非常紧密。内存管理对操作系统来说是一件非常复杂的事情,因为首先内存容量很大,其次内存需求在时间和大小块上没有规律(操作系统上运行着的几十、几百、几千个进程随时都会申请或者释放内存,申请或者释放的内存块大小随意)。
(2)常用的数据结构:数组、结构体、栈、队列、堆、链表、哈希表、二叉树、图等。
15.2.内存管理之数组
(1)当一个程序中有很多个类型相同、意义相关的变量需要管理,这个时候如果使用单独的变量来存放会使程序组织起来很乱,这种情况下就可以使用数组更好的管理该类数据。
(2)数组的优势:数组比较简单;通过下标访问;可以随机访问。数组的缺陷:数组中所有元素类型必须相同;数组大小必须定义后给出并且一旦确定不能再改。
15.3.内存管理之结构体
(1)结构体的出现是为了解决数组的”数组中所有元素类型必须相同”这一缺陷。当数据包中元素类型相同时推荐使用数组;当数据包中元素类型不同时推荐使用结构体。
(2)在linux系统内核代码中经常使用结构体内嵌指针实现面向对象,譬如以下包含了函数指针的结构体就类似于面向对象中的class:
15.4.内存管理之栈
(1)C语言中用栈来保存局部变量,一般人们所说的堆栈就是指栈;队列是先进先出的线性表,简称为FIFO表。栈管理内存的好处是方便,内存分配和最后回收都由C语言自动完成,不用程序员操心。
(2)栈管理内存的特点(小内存、自动化):先进后出FILO(first in last out)栈的特点是入口即出口,只有一个口,另一个口是堵死的,所以先进去的必须后出来。队列管理内存的特点:先进先出 FIFO(first in first out)队列的特点是入口和出口都有,必须从入口进去,从出口出来,所以先进去的必须先出来,否则就堵住后面的。
(3)栈的应用举例:C语言中的局部变量是用栈来实现的。我们在C中定义一个局部变量时,编译器会在栈中分配一段空间给这个局部变量用,栈顶指针会移动给出空间,对应栈的操作是入栈(栈指针的移动和内存分配是自动的,即栈自己完成,不需要人写代码去操作);然后等我们函数退出的时候,局部变量要灭亡,栈顶指针会移动释放空间,对应栈的操作是弹栈(出栈),(栈指针的移动和内存释放也是自动的,也不需要人写代码干预)。
(4)C语言中,定义局部变量时如果未初始化,则值是随机的,为什么?定义局部变量,其实就是在栈中通过移动栈指针来给程序提供一个内存空间和这个局部变量名绑定。因为这段内存空间在栈上,而栈内存是反复使用的,所以说使用栈来实现的局部变量定义时如果不显式初始化,值就是脏的。C语言是通过一个小手段来实现局部变量的初始化的,譬如int a = 15;(局部变量定义时初始化),C语言编译器会自动把这行转成:int a;(局部变量定义)和a = 15;(普通的赋值语句)。
(5)栈的约束(预定栈大小不灵活,怕溢出):栈是有大小的,所以栈内存大小不好设置,如果太小怕溢出,太大怕浪费内存,(这个缺点有点像数组),栈的溢出危害很大,一定要避免。所以我们在C语言中定义局部变量时不能定义太多或者太大(譬如不能定义局部变量时int a[10000]; 使用递归来解决问题时一定要注意递归收敛)。
15.5.内存管理之堆
(1)堆的特点就是自由(随时申请、释放;大小块随意)。堆内存是操作系统划归给堆管理器(操作系统中的一段代码,属于操作系统的内存管理单元)来管理的,然后向使用者(用户进程)提供API(malloc和free)来使用堆内存。使用堆内存的情况:当我们需要的内存容量比较大时;需要反复使用及释放内存时;很多数据结构(譬如链表)的实现。
(2)堆管理内存的特点(大块内存、手工分配&使用&释放):容量不限(常规使用的需求容量都能满足);申请及释放都需要手工进行(需要程序员写代码明确进行申请malloc及释放free)。如果程序员申请内存并使用后未释放,这段内存就丢失了(在堆管理器的记录中,这段内存仍然属于你这个进程,但是进程自己又以为这段内存已经不用了,再用的时候又会去申请新的内存块,这就叫吃内存),称为内存泄漏。在C/C++语言中,内存泄漏是最严重的程序bug,这也是别人认为Java/C#等语言比C/C++优秀的地方。
(3)C语言操作堆内存的接口(malloc free):堆内存释放时最简单,直接调用free释放即可( void free(void *ptr););堆内存申请时,有3个可选择的类似功能的函数:malloc, calloc, realloc(void *malloc(size_t size); 或void *calloc(size_t nmemb, size_t size);nmemb个单元,每个单元size字节 或void *realloc(void *ptr, size_t size);改变原来申请的空间的大小的)
(4)数组定义时必须同时给出数组元素个数(数组大小),而且一旦定义再无法更改。在Java等高级语言中,有一些语法技巧可以更改数组大小,但其实这只是一种障眼法。它的工作原理是:先重新创建一个新的数组大小为要更改后的数组,然后将原数组的所有元素复制进新的数组,然后释放掉原数组,最后返回新的数组给用户;堆内存申请时必须给定大小,然后一旦申请完成大小不变,如果要变只能通过realloc接口。realloc的实现原理类似于上面说的Java中的可变大小的数组的方式。
(5)堆的优势和劣势(管理大块内存、灵活、容易内存泄漏):优势:灵活。劣势:需要程序员去处理各种细节,所以容易出错,严重依赖于程序员的水平。