STL Allocator Source Learning

Source: Internet
Author: User

Overview

Introduce several allocator of the source code realization: Simple to operator new and operator Delete to encapsulate implementation, VS2015 implementation, STLport implementation, imitate STLport implement memory pool.

1. Reference

http://www.cplusplus.com/reference/memory/allocator/
"STL Source Code Analysis"
"C + + Primer Fifth Edition"
"Generic Programming and the STL" (Generics programming and STL)
Msdn

2. Introduction

Std::allocator is the memory configurator used by STL containers and is the only predefined memory configurator for standard libraries.

3. Implementation of a: The simplest implementation of 3.1 program implementation

Template<class t>class allocator{public://1, why do you need the following members, what role?    typedef T VALUE_TYPE;    typedef t* Pointer;    typedef const T* Const_pointer;    typedef t& Reference;    typedef const t& Const_reference;       typedef size_t SIZE_TYPE;    size_t is an unsigned integer//ptrdiff_t is a signed integer that represents the type of pointer subtraction result of a typedef ptrdiff_t DIFFERENCE_TYPE;    2. What is this for, and why is the template u, not the same as the allocator T?    Template<class u> struct Rebind {typedef allocator<u> other;    };    The default constructor, do nothing allocator () noexcept {}//generalization of the constructor, also do nothing//3, why need this generalization of the constructor, different types of allocator copy appropriate? Template<class u> Allocator (const allocator<u>&) noexcept {}//destructor, do nothing ~allocator () No except {}//Return object addresses pointer address (reference val) const Noexcept {//non-const version call const version, see EFFECTI   ve C + + clause 3 return const_cast<reference> (Address (Static_cast<const_reference> (Val))); }//Return object address Const_pointer const_reference val Const noexcept {return &val;    }//application memory, count refers to the number of objects, not the number of bytes.    4. What does hint do? Pointer allocate (size_type count, allocator<void>::const_pointer hint = nullptr) {return static_cast<p    Ointer> (:: operator new (count * sizeof (VALUE_TYPE)));    }//Free memory void deallocate (pointer ptr, size_type count) {:: operator delete (PTR);  }//Configurable maximum (refers to the number of objects, not bytes) Size_type max_size () const noexcept {return (static_cast<size_type> (-1)/    sizeof (Value_type)); }//Construction object, args is the template parameter package, see "C + + Primer" 5th Edition 16.4 section template <class U, class ... args> void Construct (u* p, args&&. Args) {:: New ((void *) p) U (::std::forward<args> (args)).    ..); }//destructors template <class u> void Destroy (u* p) {p->~u ();//The original template can also use}};//5, why the Void into The line of specificity? Template<>class allocator<void>{public:typedef void ValuE_type;    typedef void *pointer;    typedef const VOID *const_pointer;    Template <class u> struct rebind {typedef allocator<u> other; };};

3.2 Questions and Answers

1, STL specifications, while these type in the iterator and traits technology is useful.

2. Excerpt from MSDN:A structure that enables a allocator for objects of one type to allocate storage for objects of another type.
This structure are useful for allocating memory for type that differs from the element type of the container being implemen Ted.
The member template class defines the type other. Its sole purpose are to provide the type name allocator<_other>, given the type name Allocator<type>.

For example, given an allocator object al of type A, you can allocate an object of type _other with the expression:
A::rebind<other>::other (AL). Allocate (1, (Other *) 0)
Or, you can name it pointer type by writing the type:
A::rebind<other>::other::p ointer

Example: A list that holds an int list<int> The list stores an object that is not an int itself, but a data structure that holds an int and also contains pointers to the front and back elements. So, List<int, how does allocator<int>> know about allocating this internal data structure? After all, allocator<int> only knows the space to assign an int type. This is the problem that rebind to solve. With allocator<int>::rebind<_node> () you can create an allocator for allocating _node type space.

3, the allocator class template parameter only one, represents the assigned element type, if the allocator encapsulates only the memory allocation policy and is not related to the element type, the definition of the generic copy construction seems to be nothing unreasonable, and if not defined as a generic rebind will not be used. The construct member function and the Destroy member function are also generic, and the conditions of use of the allocator are particularly lenient.

4, hint
Either 0 or a value previously obtained by another call to allocate and not yet freed with deallocate.
When it was not 0, this value could be used as a hint to improve performance by allocating the new block near the one specifi Ed. The address of an adjacent element is often a good choice.

5, only void * variable, no void variable, no void& variable, typedef void Value_type and so on.

4. Implementation of the implementation of the second: VS2015 4.1 program implementation (partial and implementation of a similar content omitted)

Template<class _ty>class allocator{//Generic Allocator for objects of class _typublic:static_assert (!is_const <_ty>::value, "the C + + standard forbids containers of a const elements" "Because Allocator<const t> is ill-formed ."); Template<class _other>allocator<_ty>& operator= (const allocator<_other>&) {//Assign From a related allocator (does nothing) return (*this);} void deallocate (pointer _ptr, Size_type _count) {//Deallocate object at _ptr_deallocate (_ptr, _count, sizeof (_ty));} __declspec pointer allocate (Size_type _count) {//allocate array of _count Elementsreturn (Static_cast<pointer> (_ Allocate (_count, sizeof (_ty))));} __declspec pointer allocate (size_type _count, const void *) {//allocate array of _count elements, ignore Hintreturn (alloc Ate (_count));} ......};

4.2 Explanation of the problem

1, static_assert:tests a software assertion at compile time. If The specified constant expression is false, the compiler displays the specified message and the compilation fails with Error C2338; Otherwise, the declaration has no effect.

2, __declspec:microsoft specific. Tells the compiler not to insert buffer overrun security checks for a function.

3, the implementation here is also relatively simple, just the memory allocation function (_allocate) and the release function (_deallocate) for a simple encapsulation.

implementation of 4.3 _allocate and _deallocate

The implementation of the direct copy comes as follows

inline_declspec_allocator void *_allocate (size_t _count, size_t _sz,bool _try_aligned_allocation = True) {//Allocate Storage for _count elements of size _szvoid *_ptr = 0;if (_count = = 0) return (_ptr);//Check Overflow of multiplyif ((size _t) ( -1)/_SZ < _count) _xbad_alloc ();//Report No memoryconst size_t _user_size = _count * _SZ; #if defined (_m_ix86) | | Defined (_m_x64) if (_try_aligned_allocation&& _big_allocation_threshold <= _user_size) {//Allocate large Blockstatic_assert (sizeof (void *) < _big_allocation_alignment, "BIG allocations should at least match vector register s Ize "), const size_t _block_size = _non_user_size + _user_size;if (_block_size <= _user_size) _xbad_alloc ();//Report No M Emoryconst uintptr_t _ptr_container =reinterpret_cast<uintptr_t> (:: operator new (_block_size)); _SCL_SECURE_ Always_validate (_ptr_container! = 0); _ptr = Reinterpret_cast<void *> ((_ptr_container + _NON_USER_SIZE) & ~ (_ big_allocation_alignment-1)); static_cast<uintptr_t *> (_PTR) [-1] = _ptr_container; #ifdef _debugstatic_cast<uintptr_t *> (_ptr) [-2] = _big_allocation_sentinel; #endif/* _DEBUG */}else #endif/* defined (_m_ix86) | | Defined (_m_x64) */{//allocate Normal BLOCK_PTR =:: operator new (_user_size); _scl_secure_always_validate (_ptr! = 0);} return (_PTR);} FUNCTION _deallocateinlinevoid _deallocate (void * _ptr, size_t _count, size_t _SZ) {//deallocate storage for _count ele ments of size _SZ #if defined (_m_ix86) | | Defined (_m_x64) _scl_secure_always_validate (_count <= (size_t) ( -1)/_SZ); Const size_t _user_size = _count * _SZ;IF (_B Ig_allocation_threshold <= _user_size) {//deallocate large blockconst uintptr_t _ptr_user = reinterpret_cast< Uintptr_t> (_ptr); _scl_secure_always_validate ((_ptr_user & (_big_allocation_alignment-1)) = = 0); Const UINTPTR _t _ptr_ptr = _ptr_user-sizeof (void *); const uintptr_t _ptr_container =*reinterpret_cast<uintptr_t *> (_Ptr_ptr); #ifdef _debug//If The following asserts, it likely mEANs that we is performing//an aligned delete in memory coming from an unaligned allocation._scl_secure_always_validate ( reinterpret_cast<uintptr_t *> (_PTR_PTR) [-1] ==_big_allocation_sentinel); #endif/* _DEBUG *///Extra Paranoia on aligned allocation/deallocation_scl_secure_always_validate (_ptr_container < _ Ptr_user); #ifdef _debug_scl_secure_always_validate (2 * sizeof (void *) <= _ptr_user-_ptr_container); #else/* _DEBUG */_scl_secure_always_validate (sizeof (void *) <= _ptr_user-_ptr_container); #endif/* _DEBUG */_scl_secure_always_validate (_ptr_user-_ptr_container<= _non_user_size); _Ptr = reinterpret_cast <void *> (_ptr_container);} #endif/* defined (_m_ix86) | | Defined (_m_x64) */::operator Delete (_ptr);}
The above code has a lot of typedef and macros, there are some judgments and asserts, it looks more complex, the following is a streamlined implementation (only the more critical parts)

void *_allocate (size_t _count, size_t _SZ, bool _try_aligned_allocation = true) {//Allocate storage for _count elements of    Size _SZ void *_ptr = 0; Calculate the number of bytes that require memory const size_t _user_size = _count * _sz;//_big_allocation_threshold is 4096 larger than this size of memory blocks need to be aligned.    1, why 4096 as the boundary? if (_try_aligned_allocation && _big_allocation_threshold <= _user_size) {//Allocate large memory blocks//_big_allocation _alignment Large Memory Alignment +//_non_user_size for (2 * sizeof (void *) + _big_allocation_alignment-1) i.e. two pointer size plus con        St size_t _block_size = _non_user_size + _user_size;            Here the address is converted to integer for bitwise operation, uintptr_t may be unsigned int (32 bits), or unsigned long long (64-bit) const uintptr_t _ptr_container =        Reinterpret_cast<uintptr_t> (:: operator new (_block_size));        Gets the alignment address, with a low 5-bit zeroing.        2, why is the 32-byte alignment? _ptr = Reinterpret_cast<void *> ((_ptr_container + _non_user_size) & ~ (_big_allocation_alignment-1))        ; Place a real memory block starting address with a position in the _non_user_size       static_cast<uintptr_t *> (_PTR) [-1] = _ptr_container;    } else {//Allocate general memory block _PTR =:: operator new (_user_size); } return (_PTR);} void _deallocate (void * _ptr, size_t _count, size_t _SZ) {//deallocate storage for _count elements of size _SZ const SI    ze_t _user_size = _count * _SZ; if (_big_allocation_threshold <= _user_size) {//Free large memory block//convert address to integer type for subtraction operation Const uintptr_t _ptr_use        R = reinterpret_cast<uintptr_t> (_PTR);        Const uintptr_t _ptr_ptr = _ptr_user-sizeof (void *); Gets the true memory block starting address of the _non_user_size stored in const uintptr_t _ptr_container = *reinterpret_cast<uintptr_t *> (        _PTR_PTR);    _ptr = reinterpret_cast<void *> (_ptr_container); }//Real free Memory:: operator delete (_ptr);}

Explanation of the problem

1.

2.

5. Implementation three: implementation in STLport

Implementation is too simple, just to operator new and operator delete do a simple package, implementation of two is also relatively simple, Microsoft seems to be the memory allocation strategy at the bottom. If you need to know more about the delicate memory allocation policy (memory pool), refer to the implementation in STLport.

STLport in the implementation of the analysis see: "STL Source Analysis"-Houtie 2.2.6-2.2.10

6. Implementation of four: implementation with memory pool 6.1 memory pool

According to the "STL Source Analysis"-Houtie 2.2.6-2.2.10 idea, the following self-realization of a memory pool, regardless of thread safety.

Memory pool allocates memory, and when the required memory size is greater than 128, call operator new to allocate memory, otherwise allocate from the idle list, thus avoiding memory fragmentation caused by too many small blocks of memory and the additional overhead of managing memory, causing low memory utilization.

The first is the management of the data structure of the memory pool, the memory pool maintains 16 idle lists, in order to facilitate management (allocation and recycling), the size of each memory block is 8 multiples, respectively, 8,16,24,...,128. When allocating memory, if the size is not a multiple of 8, the requirement is raised to a multiple of 8 and then allocated from the corresponding free list. In order to maintain the linked list structure in the idle list, the former sizeof (void *) bytes of the free block store the address of the next free block node, and when the free block is allocated, the free block is removed from the idle list, so this practice does not affect the user, and does not require additional memory as the node's next pointer.

Class Memorypool{private:    //Small memory block size is a multiple of ms_align    constexpr static size_t ms_align = 8;    The upper limit of small memory block size    constexpr static size_t ms_maxbytes =;    Number of free memory block lists    constexpr static size_t ms_nfreelists = ms_maxbytes/ms_align;private:    //Memory block idle list, m_freelists[0] is a memory block list of size 8, m_freelists[1] is a size of 16    ... void *m_freelists[ms_nfreelists];p rivate:    //Memory pool allocation block    void *m_pfreestart;    void *m_pfreeend;};
Complete memory pool definition and implementation:

Class Memorypool{public:memorypool (): m_freelists{nullptr}, M_pfreestart (nullptr), M_pfreeend (nullptr), M_heapsize (    0) {}//Allocate memory void *allocate (size_t nbytes); Reclaim memory void deallocate (void *ptr, size_t nbytes);p rivate://Small memory block size is a multiple of ms_align constexpr static size_t Ms_al    IGN = 8;    Upper limit of small memory block size constexpr static size_t ms_maxbytes = 128; Number of free memory block list constexpr static size_t ms_nfreelists = Ms_maxbytes/ms_align;private://Memory block idle list, m_freelists[0] is 8 size    The memory block list, m_freelists[1] is size 16 ... void *m_freelists[ms_nfreelists];p rivate://Memory pool char *m_pfreestart;    Char *m_pfreeend; This thought m_heapsize is not used, originally allocated memory for memory pool can be used as a factor to calculate the increment size_t m_heapsize;private://bytes up to ms_align multiple size_t roundUp (size    _t nbytes) {return (nbytes + ms_align-1) &~ (ms_align-1); }//Based on the size of the memory block should use the first few freelist, starting from 0 size_t freelistindex (size_t nbytes) {return (nbytes + ms_align-1)/    ms_align-1; }private://re-nbytes the idle list ofWith idle nodes, Nbytes is a multiple of ms_align void *refill (size_t nbytes); Get memory from memory pool, nbytes is ms_align multiple void *chunkalloc (size_t nbytes, int &nobjs);}; void * Memorypool::allocate (size_t nbytes) {if (Nbytes > Ms_maxbytes) {//when memory block is large, directly from system assign return:: Opera    Tor New (nbytes);    }//Get nbytes size belongs to the idle list void **pfreelist = M_freelists + freelistindex (nbytes);    void *result = *pfreelist;    if (result = = nullptr) {//free block is not available in the idle list, you need to reassign return refill (RoundUp (nbytes));    }//have free blocks to remove the free block from the idle list *pfreelist = *reinterpret_cast<void **> (result); return result;} From this function can be seen in the recovery of memory does not return memory to the system, the idle list of memory only increased//considering that the allocated memory is fragmentation level, not in extreme cases of idle memory is not too much, so do not consider returning memory to the system void MemoryPool::d eallocate (void *ptr, size_t nbytes)    {if (Nbytes > Ms_maxbytes) {///and allocate correspond, directly to the system recycle: operator delete (PTR);    }//Get nbytes size belongs to the idle list void **pfreelist = M_freelists + freelistindex (nbytes);    Rejoin the memory block to the idle list *reinterpret_cast<void **> (PTR) = *pfreelist; *Pfreelist = ptr;}    void * Memorypool::refill (size_t nbytes) {int nobjs = 20;    Attempts to get NOBJS memory blocks from the memory pool may result in less than nobjs void *pchunk = Chunkalloc (nbytes, NOBJS); More than one piece, except for the first block join the idle list if (Nobjs > 1) {//Get nbytes size of the idle linked list void **pfreelist = M_freelists + Freel        Istindex (nbytes);        void *pnext = Reinterpret_cast<char *> (pchunk) + nbytes;        Connect the second block to the table header *pfreelist = Pnext;            for (int i = 2;i < nobjs;++i) {void **pcurrent = Reinterpret_cast<void **> (pnext);            Pnext = Reinterpret_cast<char *> (pchunk) + nbytes;        Connect the Pnext to the idle list *pcurrent = Pnext;    }//Idle list The last next pointer is empty *reinterpret_cast<void **> (pnext) = nullptr; }//Return the first block to the user return pchunk;}    void * Memorypool::chunkalloc (size_t nbytes, int &nobjs) {size_t nneedbytes = nbytes * NOBJS;    size_t nfreebytes = M_pfreeend-m_pfreestart;    void *result; if (nneedbytes <= nfreebytes)   {//Memory pool also has enough memory result = M_pfreestart;    M_pfreestart + = Nneedbytes;        } else if (nbytes <= nfreebytes) {///memory pool memory is also sufficient for one or more memory blocks NOBJS = nfreebytes/nbytes;        result = M_pfreestart;    M_pfreestart + = nbytes * NOBJS; } else {//Memory pool memory is not enough for a block of memory if (Nfreebytes > 0) {//Memory pool also has a fraction, add a fraction to the appropriate idle list//From here can be seen            Increase the memory block to a multiple of ms_align the design is so delicate. void **pfreelist = m_freelists + freelistindex (nfreebytes);            *reinterpret_cast<void **> (M_pfreestart) = *pfreelist;            *pfreelist = *pfreelist;        When the following operator new memory allocation fails, the M_pfreestart state is guaranteed to be legal m_pfreestart + = nfreebytes; }//The amount of memory requested by the system, twice times the amount required plus additional increments//STLport is calculated in this way, it may be more efficient size_t Nbytestoget = 2 * nneedbytes + roundUp        (M_heapsize >> 4);        M_pfreestart = Reinterpret_cast<char *> (operator new (nbytestoget));        M_heapsize + = Nbytestoget; M_pfreeend = M_pfreestart + nbytesToget;    Recursive call, very possible procedure return Chunkalloc (Nbytes, NOBJS); } return result;

6.2 Allocator implementation with a memory pool

Here and implement one of the same parts omitted, give only a few operations to allocate and free memory:

Template<class t>class allocator{private:    static MemoryPool pool;public: ...//with the implementation of a    pointer allocate ( Size_type count, allocator<void>::const_pointer hint = nullptr)    {        return static_cast<pointer> ( Pool.allocate (Count * sizeof (VALUE_TYPE)));    Free memory    void deallocate (pointer ptr, size_type count)    {        pool.deallocate (PTR, count);    } ...//with implementation one};




STL Allocator Source Learning

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.