C/C++动态存储器分配

来源:互联网 发布:freebsd 安装python 编辑:程序博客网 时间:2024/05/29 14:06

1. mmap和munmap

#include <sys/mman.h>
void *mmap(void *start,size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *start,size_t length);

        mmap将一个文件或者其它对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的大小之和,最后一个页不被使用的空间将会清零。mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。

        munmap用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。当进程结束利用exec相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述词时不会解除映射。返回值如果解除映射成功则返回0,否则返回-1,错误原因存于errno中错误代码EINVAL


2. malloc和free

        虽然可以使用低级的mmap和munmap函数来创建和删除虚拟存取器的区域,但是C程序员还是觉得用动态存储器分配器(dynamic memory allocator)更方便,也有更好的移植性。

        动态存取器分配器维护着一个进程的虚拟存储器区域,称为堆(heap)。分配器将堆视为一组不同大小的块(block)的集合来维护。每个块就是一个连续的虚拟存储器片(chunk),要么是已经分配的,要么是空闲的。

        分配器有两种基本风格。两者风格都要求应用显示分配块,他们的不同之处在于由两个实体来负责释放已分配的块。显示分配器(explicit alloctor),要求应用显示地释放任何已分配的块。隐式分配器(implicit allocator),又称作垃圾收集器(garbage collector),分配器检测一个已分配块何时不再被程序所使用,那么就释放这个块。

        malloc遇到问题(例如,程序请求的存储器块比可用的虚拟存储器还要大),那么他就返回NULL,并设置errno。malloc不初始化它返回的存储器。如果想要已初始化的动态存储器的应用程序可用使用calloc,calloc是一个基于malloc的瘦包装函数,它将分配的存储器初始化为零。想要改变一个以前已分配的大小,可用使用realloc函数。

        动态存储器分配器,例如malloc,可用通过使用mmap和munmap函数,显示地分配和释放堆存储器,或者还可以使用sbrk函数(sbrk控制内核中brk指针实现heap的增缩),如果成功,sbrk就返回brk的旧值,否则返回-1,并将errno设置为ENOMEM。

        Free函数释放已分配的堆块。在C程序的上下文中,应用调用malloc,但没调用Free,垃圾收集器识别垃圾块,并相应地调用free,将这些块放回到空闲链表中。


3. 垃圾收集器的基本知识

        垃圾收集器将存储器视为一张有向可达图(reachability graph),图中节点被分成一组根节点和一组堆节点。每个堆节点对应于堆中的一个已分配块。有向边p->q意味着块p中的某个位置指向块q某个位置。根节点对应于这样一种不在堆中的位置,他们包含指向堆的指针。这些位置可以是寄存器、栈里的变量,或是虚拟存储器中读写数据区内的全局变量。垃圾收集器的角色是维护可达图的某种表示,并通过释放不可达节点,将他们返回给空闲链表,来定期地回收他们。

        像ML和Java这样的语言的垃圾收集器,对应用如何创建和使用指针有很严格的控制,能够维护可达图的一种精确的表示,因此也就能够回收所有的垃圾。然而,诸如C和C++这样的语言的收集器通常不能维护可达图的精确表示。这样的收集器也叫做保守的垃圾收集器(conservative garbage collector)。从某种意义上来说他们是保守的,即每个可达快都被正确的标记为可达了,而一些不可达的块却可能被错误的标记为可达。

        无论何时需要堆空间,应用都会用通常的方式调用malloc。如果malloc找不到一个合适的空闲块,那么它就调用垃圾收集器,希望能收到一些垃圾到空闲链表中。收集器识别垃圾块,并通过调用Free函数将他们返回给堆。


4. new和delete

        C++内存分配时一种类型化操作:new为特定类型分配内存,并在新分配的内存中构造对象。new表达式自行运行合适的构造函数来初始化每个动态分配的类类型对象。

        首先需要对new和delete表达式怎样工作有更多的理解。当使用new表达式

string* sp = new string("initialized")

的时候,实际上发生了三个步骤。首先,该表达式调用名为operator new的标准库函数,分配足够大的原始的未类型化的内存,以保存指定类型的一个对象;接下来,运行该类型的一个构造函数,用指定初始化构造对象;最后,返回指向新分配并构造的对象的指针

delete sp;

当使用delete表达式delete sp;删除动态分配对象的时候,发生两个步骤。首先,对sp指向的对象运行适当的析构函数;然后调用operator delete的标准库函数释放该对象所用内存


5. 了解new-handler的行为

        当operator new无法满足某一内存分配需求时,它会抛出异常。以前它会返回一个null指针,某些旧式编译器目前也还那么做。你还是可以取得旧行为。

        当operator new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler,那么声明于<new>的标准程序库函数:

namespace std{    typedef void (* new_handler)();    new_handler set_new_handler(new_handler p) throw();}
new_handler是应该以无输入无返回的函数指针。

        当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够内存。

void * operator new(std::size_t size) throw(std::bad_alloc){     using namespace std;     if(size==0)  size=1;     while(true){           尝试分配size byte;           if(success) return a pointor;           //if failed           new_handler globalHandler=set_new_handler(0); //获取当前的new_handler           set_new_handler(globalHandler);                     if(globalHandler) (*globalHandler)();           else throw std::bad_alloc();     }}

        一个良好设计的new-handler函数必须做以下事情:

  • 让更多的内存可以被使用。这便造成operator new内的下一次内存分配动作可能成功。
  • 安装另一个new-handler。如果目前这个new-handler无法取得更多可用内存,或者它知道另外哪个new-handler有此能力。果真如此,目前这个new-handler就可以安装另外那个new-operator以替换自己(调用set_new_handler)。
  • 卸除new-handler,就是将null指针传给set_new_handler。一旦没有安装任何new_handler, operator new会在内存分配不成功时抛出异常。
  • 抛出bad_alloc(或派生自bad_alloc)的异常。
  • 不返回,通常调用abort或exit




0 0
原创粉丝点击