8.内存管理的概述

来源:互联网 发布:最后一次网络歌手 编辑:程序博客网 时间:2024/05/21 10:15

8.内存管理的概述

当程序被加载到内存的时候,它在内存中会大致被组织成个部分:代码区,静态存储区和动态存储区。代码区存放的是将要执行的程序的机器语言表示,包括组成程序的各种用户自定义函数和系统调用函数。关于静态存储区和动态存储区:The word static refers to things thathappen at compile time and link time when the program is constructed—as opposedto load time or run time when the program is actually started;The term dynamic refers to things that take place when a program is loaded andexecuted. 就是说静态存储区和动态存储区的主要的区别在于空间分配的时间不同,静态存储区的空间实在程序编译和连接的时候分配的,而动态存储区分配的空间是在程序调入和执行的时候分配的。

静态存储区中主要存放全局的(global),静态的(static)数据。其可以分为两部分,一部分用来存放已经初始化的数据,另一部分用来存放没有初始化的数据(这部分数据会被默认初始化为0,这块区域称为BSS(Block Started by Symbol)段,是用来存放程序中未初始化的全局变量的一块内存区域)。动态存储区分为两部分,即堆(heap)和栈(stack)。堆中主要存放在程序运行过程中调用calloc和malloc函数动态分配的内存空间,当在程序中调用calloc和malloc函数来为程序分配新的空间时,堆空间便会向上增长。栈用来存放局部变量,用来传递函数的参数,并用来记录函数的返回地址(当函数执行结束,根据改地址程序可以跳转到相应的指令出继续执行)。当一个新的函数被调用,会有一个新的栈frame被加入到占空间当中,这时栈向下增长。


通常来说,堆和栈在进程的虚拟地址空间的两端。当被访问的时候,栈地址空间就会增长,最大可以到一个由内核设定的值(可以通过setrlimit(RLIMIT_STACK,...)进行调整)。而堆空间的增长则是由于内存分配函数(calloc和malloc)触发了brk()和sbrk()系统调用,这样会使操作系统将更多的物理内存页面映射到该进程的虚拟地址空间。

堆空间和栈空间的管理是由操作系统来实现的。通常,游戏等对性能要求较高的应用会有自己的内存管理方案。(比如,一次从堆中申请大块的空间,然后内部分配,这样就避免了依赖于操作系统的内存管理。)

1.1     栈

1.1.1       介绍

在计算机体系结构中,栈是一个后进先出的数据结构。在大多数现在计算机系统中,每一个线程多会有一个保留的内存区域作为栈来使用。当函数执行的时候,它会将一些状态信息放在栈顶;当函数退出的时候,它负责将数据从栈中移除。栈的一个重要用途就是用来保存函数调用的地址,来保证return语句能够返回到正确的位置。(当然,还有其他用途。)

因为栈中的数据时后进先出的,这种方式比较简单。因此,通常栈的内存分配要比堆的内存分配(也就是常说的动态内存分配)要快。另外一个特性是,当函数退出时,它在栈中占用的内存空间将会被自动释放。如果其中的数据是不需要的,这对于编程人员来说是非常方便的。当然,如果有些数据还需要被保留,那么这些数据一定要在函数退出之前被拷贝到其他地方。因此,基于栈的内存分配,适合于存放临时数据,或者是函数退出之后不再需要的数据。

一个栈是由一些栈frame组成的。这是一种和机器相关的保存着子程序状态信息的数据结构。每一个栈frame对应着一个还没有被return语句终止的子程序。例如,一个名为DrawSquare的程序调用了一个名为DrawLine的子程序,栈顶的格局便如下:


栈比较快的原因是,它的访问方式比较简单,这样比较容易从中分配内存,而堆有着复杂得多的内存分配和释放机制。另外一方面,栈中的每一个字节都会被经常重复使用,这样,在实现上,它们可能会被映射到处理器的cache,这也是栈计较快的原因。

(1)在栈中创建的变量在超出作用域后便会被自动释放。

(2)栈中分配的变量要比堆中要快的多。

(3)栈是用一个真正的栈数据结构实现的,用来存储局部变量,函数的返回地址,并用来参数传递。

(4)如果超量使用会导致栈溢出(比如无限递归,超大的分配等)。

(5)栈中的数据不用指针就可以访问。

(6)如果在运行之前,你知道需要多大的空间来存放数据,而且这个空间不会导致溢出,那么这时候应该使用栈。

 (7)通常,在程序开始的时候,一个栈空间的最大值就已经被确定了。

 (8)在C语言中,可以是用alloca实现变量长度的内存分配,这会在栈上分配空间,而不会想alloc一样在堆上分配空间。尽管这样的内存空间在return之后也会被释放,但是用来做buffer还是很有用的。

1.1.2       栈溢出

栈相关的内存错误是最糟糕的。如果使用堆内存分配,在越界时,可能会触发一个segmentfault(当然,也不是所有时候都会这样,比如恰好两个分配的空间时连续的)。但是,由于在栈上创建的变量相互之间都是连续的,如果在写的时候越界会改变另一个变量的值。现在,我感觉只要是我的程序不按逻辑运行了,我就知道很可能是缓冲区溢出了。

1.1.3       堆

堆包含一个使用和空闲的块空间列表。新的内存空间分配(new 或者 malloc)通过在一个空闲的块上创建一个合适的内存块来实现。这需要更新堆上的块列表。这些关于堆上块的元信息也是存在堆上,往往是在每一块前面的一小块区域。

l  堆的大小在程序启动的时候就确定了,但是也可以随着空间需要而增长(分配器会向操作系统申请更多的内存空间。)

l  堆上的变量必须被手动销毁,它们永远不会超出作用域。它们可以用delete,delete[]或者free来进行释放。

l  堆上的变量要比栈上的变量空间分配要慢

l  堆上的空间是由程序决定按需分配的。

l  当执行很多分配和释放操作之后,会产生内存碎片

l  当申请太大的空间时,可能会分配失败。

l  如果在运行时不知道需要多大的空间,或者是需要分配很多的空间,这个时候应该使用堆分配。

l  堆分配会产生内存泄露。

1.2     内存泄露

1.2.1       内存泄露介绍

当一个计算机程序占用了内存,但是使用完毕却没有将其释放给操作系统时,便出现了内存泄露。内存泄露只能通过程序员检查源代码查出。内存泄露会减少可用内存的数量,从而影响程序的性能。最坏的情况下,过多的空闲内存被占用,将导致设备或者系统的全部或部分停止正常工作。

内存泄露或许不是那么严重,甚至是可以通过常规手段探测到的。在现代操作系统中,被一个程序占用的正常的内存在该程序结束时会被释放。这意味着一个短时间运行的内存泄露可能不会被注意到,也很少会引起严重的问题。

通常,内存泄露是由于动态分配的内存become unreachable。这种常见的内存泄露的bug导致了一些探测unreachable内存的debug工具的流行。比如IBM Rational Purify,BoundsChecker,Valgrind,Insure++,memwatch等C/C++内存debug工具。“保守”的垃圾回收机制也被加到缺少这类原生feature的程序语言中。C/C++程序中也有负责这类功能的库。一个保守的收集器能够发现并释放大部分unreachable内存,但不是全部。

0 0