没事的时候在看英文版的Thinking in C++ ,准备翻译部分内容,觉得本部分内容对于编程来说还是相当重要
在程序运行期间一直存在的静态变量和外部变量存在于地址的高端。每当函数被调用的时候,一个包含函数的自动变量的帧(活动记录)产生,这些帧构成了一个运行时的堆栈。
为详细解释运行时堆栈的操作,让我们会想一下计算 n 的阶乘的程序: f(n)=n*f(n-1); 。每一次调用阶乘的函数的时候,系统就在堆栈中生成一个新的帧 , 以存储方程中的局域变量。
为详细解释运行时堆栈的操作,让我们会想一下计算 n 的阶乘的程序: f(n)=n*f(n-1); 。每一次调用阶乘的函数的时候,系统就在堆栈中生成一个新的帧 , 以存储方程中的局域变量。
用作动态申请的空间位于静态变量外部变量与运行时的堆栈之间,通常由堆来实现。堆由外部变量的部分向堆栈扩张
一个程序在堆栈的上界位于堆的下界的时候可以正常的运行。一旦堆栈的上界高于堆的下界的时候(即可能是由于运行时堆栈膨胀或者动态申请要求太多),程序需求超出范围,程序的执行也立即停止。
一个程序在堆栈的上界位于堆的下界的时候可以正常的运行。一旦堆栈的上界高于堆的下界的时候(即可能是由于运行时堆栈膨胀或者动态申请要求太多),程序需求超出范围,程序的执行也立即停止。
C 中内存的初始化
对变量的声明可能引入对它的初始化。缺省的规则是变量不一定被明确的声明。在程序执行的开始,静态变量、外部变量都被赋值为 0 ,同时,自动变量的初值并不固定。包含外部变量及静态变量的存储区可能在程序执行之前被清空,并且不需要再被初始化。但是自动变量是被储存在堆栈中的,在它们被明确的赋值之前,他们含有垃圾 ―― 堆栈帧中的废弃的变量。
动态申请内存中可能含有过去动态申请的一些旧值,或者是一些曾经被用作标识堆的空间是可以利用的指针。这样, malloc() 函数并不关心它返回的内容,而另一个函数 calloc() 将作对动态存储的初始化。
3 .一个简单的堆管理方法
C 的标准库中提供了一些对堆进行管理的函数,比如我们经常用到的 malloc() 用来从堆中申请内存。与它对应的函数使 free() 函数,用来进行释放内存至堆中。如果对这两个函数应用不当,我们就会看到,程序在对中会留下许多不可访问的节点。如果内存资源不是很充足的话,我们就必须做出一些相应的修改来保证内存的清空。
在这一节中,我们将给出一个简单的实现 malloc() 和 free() 的方法。这也是许多系统实现对堆的管理的一种方法。
当一个程序开始执行的时候,它的静态变量与外部变量占据了一部分固定的储存空间。而程序的动态申请空间以及运行时堆栈就要从操作系统申请。操作系统负责对运行时堆栈的管理,同时程序负责对动态储存空间的管理,并且着重关注堆的空间的增长。我们假设,有一个名为 expand() 的系统函数用来增长堆的空间。 expand() 的参数是需要的动态存储空间,返回的是指向一块连续的内存空间的指针,或者是一个空指针表明没有合适的储存空间。
在对堆的数据结构描述中,无论一个节点是否被占用它都被连接在一个由各个节点连接而成的环状链表中。图五所示的是一个典型的节点。当程序调用 malloc() 函数来申请动态空间的时候, malloc() 函数返回了一个指向 info 中第一个字的指针。程序可以在申请的空间内存放任何数据。节点的第一部分用来标志这个节点是否被占用,并包含着一个指向其他节点的指针。
下面介绍一些关于堆的性质。首先,一个节点的大小并未明确规定,这可以归结于节点的址与下一个节点的地址的差别。第二,如果由
expand()
函数回的一段内存中没有连续的地址,这样,程序不接受这个地址并把这个节点当作已经被用的节点。另外,如果链表上两个邻近的节点都为空,就可以把两个节点连接成为一个节点。
一旦我们不再需要一些节点的空间的时候, free() 函数能够简单的通过设置 busy 位为 0 而释放空间。
访问堆需要通过一个 rover 指针来实现, rover 指针总是指向最近申请或者释放的那个节点。当需要空间的时候, rover 指针沿着链表寻找有效的空节点,并且作连接的工作。当 rover 指针找到足够的空间的时候, malloc() 函数取得一块合适的空间并且标记 busy 为 1 ,将此节点标记为堆的一个自由节点,返回 rover 作为指向连续空间的指针。如果 rover 回到了它起始的位置,这说明堆中没有足够大的空间, malloc() 用合适的参数来调用 expand() 函数,将空间归还给堆,并将 rover 指针指向一块合适的空间。
性能
堆的管理的优点是节点能够在瞬间被释放。其缺点是在申请内存的时候的最坏的时间长度令人感到窒息。如果每一次申请内存都需要走遍整个链表的话,会在 n 个节点的链表中消耗 Θ ( n2 )的时间。显然,我们不希望每次程序的运行中都有最坏情况。我们可以用统计的方法计算堆中块的大小来改善算法的性能。
4 . 总结
内存的组织在这里显得十分的复杂,看起来像是层层叠加的单元。实际上,真正的计算机的内存组织要更加复杂。关于更加深入的知识可以参阅操作系统,编译原理以及数据结构的资料。
一旦我们不再需要一些节点的空间的时候, free() 函数能够简单的通过设置 busy 位为 0 而释放空间。
访问堆需要通过一个 rover 指针来实现, rover 指针总是指向最近申请或者释放的那个节点。当需要空间的时候, rover 指针沿着链表寻找有效的空节点,并且作连接的工作。当 rover 指针找到足够的空间的时候, malloc() 函数取得一块合适的空间并且标记 busy 为 1 ,将此节点标记为堆的一个自由节点,返回 rover 作为指向连续空间的指针。如果 rover 回到了它起始的位置,这说明堆中没有足够大的空间, malloc() 用合适的参数来调用 expand() 函数,将空间归还给堆,并将 rover 指针指向一块合适的空间。
性能
堆的管理的优点是节点能够在瞬间被释放。其缺点是在申请内存的时候的最坏的时间长度令人感到窒息。如果每一次申请内存都需要走遍整个链表的话,会在 n 个节点的链表中消耗 Θ ( n2 )的时间。显然,我们不希望每次程序的运行中都有最坏情况。我们可以用统计的方法计算堆中块的大小来改善算法的性能。
4 . 总结
内存的组织在这里显得十分的复杂,看起来像是层层叠加的单元。实际上,真正的计算机的内存组织要更加复杂。关于更加深入的知识可以参阅操作系统,编译原理以及数据结构的资料。