C++中的内存分配

C++提供下面两种方法分配和释放未构造的原始内存

(1)allocator 类,它提供可感知类型的内存分配

(2)标准库中的 operator new 和 operator delete,它们分配和释放需要大小的原始未类型化的内存

C++ 还提供不同的方法在原始内存中构造和撤销对象

(1)std::allocator<T>::construct 在未构造内存中初始化对象,std::allocator<T>::destroy 在对象上运行适当的析构函数。

(2)定位 new 表达式( placement new expression),接受指向未构造内存的指针,并在该空间中初始化一个对象或一个数组。

(3)直接调用析构函数来撤销对象

(4)算法 uninitialized_fill 和 uninitialized_copy 像 fill 算法和 copy 算法一样执行,除了它们在目的地构造对象而不给对象赋值之外。

std::allocator<T>

allocator类将内存的分配及对象构造分开。分配及释放分别是 allocate 和 deallocate 。构造和析构分别是 construct 和 destroy。

std::vector<T>

标准库中的 vector 是一个动态数组。在使用时,vector 预先分配一块内存,当数据增长到预先分配的内存不够使用时,vector 采取的策略是重新分配 2 倍于当前 vector 容量大小的内存,并把所有数据复制到新地址处,旧地址所有数据进行析构,最后将这块旧的内存释放。

  1.  1 template<class T>
     2 classvector
     3 {
     4 public:
     5        vector():elements(0),first_free(0),end(0){}
     6        void push_back(const T&);
     7        //…
     8 private:
     9        static std::allocator<T> alloc;
    10        void reallocate();
    11        T* elements;
    12        T* first_free;
    13        T* end;
    14        //…
    15 };

elements 指向数组的第一个元素,first_free 指向第一个空闲的位置,end 指向数组最一个元素的下一个元素。

std::vector<T>::push_back(const T& t)

  1.  1 tmplate <class T>
     2 voidvector<T>::push_back(const T& t)
     3 {
     4     //are we out of space
     5     if(first_free == end)
     6         reallocate();// gets more space and copies existing elements to it
     7     alloc.construct(first_free,t);
     8     ++first_free;
     9 }
    10 tmplate <class T>
    11 voidvector<T>::reallocate()
    12 {
    13     std::ptrdiff_t size = first_free – elements;
    14     std::ptrdiff_t newcapacity =2* max(size,1);
    15
    16     T* newelements = alloc.allocate(newcapacity);
    17
    18     uninitialized_copy(elements,first_free,newelements);
    19
    20     for(T *p = first_free; p != elements;}
    21         alloc.destroy(--p);
    22
    23     if(elements)
    24         alloc.deallocate(elements,end-elements);
    25     elements = newelements;
    26     first_free = elements + size;
    27     end = elements + newcapacity;
    28 }

operator new 函数和 operator delete 函数

理解 new 表达式

  1. 1 //new expression
    2 string *sp =new string(“initialized”);

    首先,该表达式调用名为 operator new 的标准库函数,分配足够大的原始的未类型化的内存;

接下来,运行该类型的一个构造函数,用指定初始化式构造对象;

最后,返回指向新分配并构造的对象的指针

  1. 1 delete sp;

    首先,对 sp 指向的对象运行适当的析构函数

然后,通过调用名为 operator delete 的标准库函数释放该对象所用内存

operator new 和 operator delete 接口

  1. 1 void*operator new(size_t);
    2 void*operator new[](size_t);
    3 void*operator new(std::size_t size,void* ptr)throw();//placement
    4
    5 void*operator delete(void*);
    6 void*operator delete[](void*);

与 allocator 类的 allocate 和 deallocate 成员类似。

但是,operator new 和 operator delete 在 void* 指针而不是类型化的指针上进行操作。这就意味着,allocate 成员分配类型化的内存,不必计算以字节为单位的所需内存量,它们也可以避免对 operator new 的返回值进行强制类型转换

placement new 定位 new 表达式

placement new 在已分配的原始内存中初始化一个对象。它与 new 的其它版本的不同之处在于,它不分配内存。

定位 new 表达式的形式是:

new (place_address) type

new (place_address) type (initializer-list)

例:

  1. 1 std::allocator<std::string> alloc;
    2 string *sp = alloc.allocate(2);// allocate space to hold 2 strings
    3 new(sp) string(b,e);
    4 alloc.construct(sp +1, string(b,e));

总结

类特定的 new 和 delete

默认情况下, new 表达式通过调用由标准库定义的 operator new 版本分配内存。通过定义自己的名为 operator new 和 operator delete 的成员,类可以管理用于自身类型的内存。

编译器看到类类型的 new 或 delete 表达式的时候,它查看该类是否有 operator new 或  operator delete 成员,如果类定义(或者继承)了自己的成员 new 和 delete 函数,则使用那些函数为对象分配和释放内存;否则,调用这些函数的标准库版本。

一个内存分配器基类

通用策略:

预先分配一块原始内存来保存未构造的对象,创建新元素时,可以在一个预先分配的对象中构造;释放元素的时候,将它们放回预先分配对象的块中,而不是将内存实际返回给操作系统。

这种策略常被称为维持一个自由列表(freelist)。可以将自由列表实现为已分配但未构造的对象的链表。

  1.  1 template<class T>
     2 classCachedObj
     3 {
     4 public:
     5            void*operatornew(std::size_t);
     6            void*operatordelete(void*, std::size_t);
     7            virtual~CachedObj(){}
     8 protected:
     9            T* next;
    10 private:
    11            staticvoid add_to_freelist(T*);
    12            static std::allocator<T> alloc_mem;
    13            static T* freeStore;
    14            staticconst std::size_t chunk;
    15 }

CacheObj 只是分配和管理已分配但未构造对象的自由列表。

定义 operator new,返回自由列表的下一个元素,并将该元素从自由列表中删除。当自由列表为空的时候,operator new 将分配新的原始内存。

定义 operator delete,在撤销对象时将元素放回自由列表。

实现:

  1.  1 template<class T>
     2 void*CachedObj<T>::operatornew(size_t sz)
     3 {
     4     if(sz !=sizeof(T))  returnnullptr;
     5     if(!freeStore)
     6     {
     7           T* array = alloc_mem.allocate(chunk);
     8            for(size_t i =0; I != chunk;++i)
     9                      add_to_freelist(&array[i]);
    10      }
    11      T*p = freeStore;
    12      freeStore = freeSore->CachedObj<T>::next;
    13      return p;
    14 }
    15 template<class T>
    16 voidCachedObj<T>::add_to_freelist(T*p)
    17 {
    18    p->CachedObj<T>::next = freeStore;
    19    freeStore = p;
    20 }
    21 template<class T>
    22 voidCachedObj<T>::operatordelete(void*p)
    23 {
    24     if(p !=0)
    25            add_to_freelist(static_cast<T*>(p));
    26 }

CachedObj 其实很像 linux 的链表。除掉 private 的 static 变量和一个虚表的指针,CachedObj 其实只维护一个 T* next。它的内存布局像这样:

当继承 CachedObj 类的时候,用来实例化 CachedObj 类的模板类型将是派生类型本身。

其实就是利用模板很巧妙地把 next 指针的类型设置成子类的类型。实例化出来的类仿佛本就是一个类,没有两个类拼凑的感觉,天衣无缝。

例如:

  1. 1 template<classType>
    2 classQueueItem:
    3 publicCachedObj<QueueItem<Type>>{};

QueueItem 继承自 CachedObj,它的内存布局类似这样:

来自为知笔记(Wiz)

时间: 2024-10-12 20:09:38

C++中的内存分配的相关文章

iOS开发中的内存分配与分区

iOS开发中的内存分配与分区 关于RAM&ROM RAM与ROM就是具体的存储空间,统称为存储器. RAM(random access memory):运行内存,CPU可以直接访问,读写速度非常快,但是不能掉电存储.它又分为: 动态DRAM,速度慢一点,需要定期的刷新(充电),我们常说的内存条就是指它,价格会稍低一点,手机中的运行内存也是指它. 静态SRAM,速度快,我们常说的一级缓存,二级缓存就是指它,当然价格高一点. ROM(read only memory):存储性内存,可以掉电存储,例如

Java中的内存分配机制

Java的内存分为两种:一种是栈内存,一种是堆内存. 在函数中定义的一些基本类型变量和对象的引用都在函数的栈内存中分配.当在一个代码块中定义一个变量的时候,java就在栈中为其分配内存,当超过作用域的时候内存自动释放. 对内存用来存放new创建的对象和数组.在堆中分配的内存,由java虚拟机的垃圾回收机器管理.java的堆是运行时数据区,堆的优势是可以动态的分配内存大小,生存周期也不必事先告诉编译器,但是,由于是动态分配,存取速度慢. 栈的优势是比堆的存取速度快,仅次于寄存器,栈数据可以共享,但

c++中函数中变量内存分配以及返回指针、引用类型的思考

众所周知,我们在编程的时候经常会在函数中声明局部变量(包括普通类型的变量.指针.引用等等). 同时,为了满足程序功能的需要,函数的返回值也经常是指针类型或是引用类型,而这返回的指针或是引用也经常指向函数中我们自己声明的局部变量. 这样,程序在某些情况下就可能存在一定的问题.看似很简单的问题,通过仔细的分析,我们就能够更好的理解c++中内存分配和释放的问题. 好,废话不多说,我们进入正题.首先,简单介绍一下程序的内存区域的分配: 程序的内存分配 ①堆区(heap).这一部分主要是由程序开发人员自己

iOS程序中的内存分配 栈区堆区全局区(转)

在计算机系统中,运行的应用程序的数据都是保存在内存中的,不同类型的数据,保存的内存区域不同.一.内存分区 栈区(stack) 由编译器自动分配并释放,存放函数的参数值,局部变量等.栈是系统数据结构,对应线程/进程是唯一的.优点是快速高效,缺点时有限制,数据不灵活.[先进后出] 栈空间分静态分配 和动态分配两种. 静态分配是编译器完成的,比如自动变量(auto)的分配. 动态分配由alloca函数完成. 栈的动态分配无需释放(是自动的),也就没有释放函数. 为可移植的程序起见,栈的动态分配操作是不

Java中的内存分配与垃圾回收

一.内存分配 Java程序运行时的内存分配,按照JVM规范,包括以下几个区域:程序计数器.虚拟机栈.本地方法栈.方法区.堆.其中,前三个是线程私有的,与线程生命周期相同,线程退出内存自动回收:后两者是所有线程共享内存的,只在垃圾回收机制被触发时,被动回收. * 程序计数器,内存区域极小,是当前线程的字节码执行行号指示器: * 虚拟机栈.本地方法栈,即平时所说的“栈”,是虚拟机用来执行方法(包括Java.非Java方法)时,使用的临时内存空间,用来存储当前方法.局部变量等,全部基本类型变量,以及类

java中的内存分配原则

问题:Java中这些类.变量.字符串.方法,在内存中是怎样分配的? 首先,Java中的内存区域如下: ◆寄存器:我们在程序中无法控制 ◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中.当在一段代码块定义一个变量时,Java就在栈中 为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用.实际上,栈中的变量指向堆内存中的变量,这就是Java中的指针! ◆堆:存放用new产生的数据.在堆中分配的内存,由J

C语法中的内存分配

关于c中的内存分配问题,这个问题怎么说好呢,我感觉还是和内存四区挂上了钩,在主调函数中栈区上分配的内存空间是可以在被调函数使用的: 例: #include <stdio.h> #include <string.h> #include <stdlib.h> char *distribution(char *a) { char b[64];//被调函数中在栈区上分配一个64字节的一维数组 printf("%s\n",a);//打印字符串为 aaabbb

Linux内核中常见内存分配函数

1.原理说明 Linux内核中采用了一种同时适用于32位和64位系统的内存分页模型,对于32位系统来说,两级页表足够用了,而在x86_64系统中,用到了四级页表,如图2-1所示.四级页表分别为: l   页全局目录(Page Global Directory) l   页上级目录(Page Upper Directory) l   页中间目录(Page Middle Directory) l   页表(Page Table) 页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录

c语言中的内存分配malloc、alloca、calloc、malloc、free、realloc、sbr

C语言跟内存分配方式 (1) 从静态存储区域分配.内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在.例如全局变量,static变量. (2) 在栈上创建.在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限. (3)从堆上分配,亦称动态内存分配.程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存.动态内存的