STL Source Analysis

Chapter II Space configurationr (alloctor)

Estimated reading time 19 minutes 251 views

1. Standard interface for space configurations

1.1 General interface (generalized)

allocator::value_type
allocator::pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type

1.2 Specialized interface (specified)

InterfaceRole
allocator::rebindThe aim is to help alocator to allocate space for different types of elements, even if alocator has been designated as an object to allocate space.
allocator::allocator()Default Construct Functions
allocator::allocator(const allocator&)Copy Functions
template<class U>allocator::allocator(const allocator<U>&)Pan copy function
allocator::~allocator()Default Parsing Functions
pointer allocator::address(reference x) constreturns the address of the specified object, e. g. a.address(x) < = & x
pointer allocator::allocate(size_type n, const void* = 0)Configure space to store n-type T objects, the second parameter is primarily used to optimize memory optimization and management
allocator::deallocate(pointer p, size_type n)Memory allocated before release
allocator::allocator::max_size() constReturns the value of the maximum memory size successfully assigned
allocator::destroy(pointer p)Equivalent to p->T()

In one example.allocator::rebindComplementing:

template<class T>
class alloc;

template<typename T, typename Alloc = alloc<T>>
class MyContainer{
public:
    template<typename U>
    struct rebind{
        typedef Alloc<U> other; // Allocator type for type U
        // other represents some other objects
    };
};
C++

1.3 Design of a simple space configurer

    #include <new>          // for placement new
    #include <cstddef>      // for ptrdiff_t, size_t
    #include <cstdlib>      // for exit()
    #include <climits>      // for UINT_MAX
    #include <iostream>     // for cerr
    #include <vector>       // for test
    
    namespace T_ {
        // allocate memory
        template<class T>
        inline T* _allocate(ptrdiff_t size, T*) {
            std::set_new_handler(0);    // invoke when out of memory
            T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));  // allocate memory
            if (tmp == 0) {   // the allocated memory is 0, which means that allocating memory fails
                std::cerr << "out of memory" << '\n';
                exit(0);    // terminate processs
            }
            return tmp;     // return the pointer of the allocated memory block
        }
    
        // release memory
        template<class T>
        inline void _deallocate(T* buffer) {
            ::operator delete(buffer);  // release the allocated memory
        }
    
        // constructor
        template<class T1, class T2>
        inline void _construct(T1* p, const T2& value) {
            new(p) T1(value);           // assign value at pointer p
        }
    
        // destructor
        template<class T>
        inline void _destroy(T* ptr) {
            ptr->~T();                  // invoke the destructor of type class
        }
    
        template<class T>
        class allocator {
        public:
            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;
            typedef ptrdiff_t	difference_type;
    
            /*-----implement part of the specific API-----*/
    
            // allocator::rebind->rebind allocator of type U
            template<class U>
            struct rebind {
                typedef allocator<U> other;
            };
    
            // pointer allocator::allocate->hint used for locality
            pointer allocate(size_type n, const void* hint = 0) {
                return _allocate((difference_type)n, (pointer)0);
            }
    
            // allocator::deallocate
            void deallocate(pointer p, size_type n) { _deallocate(p); }
    
            // allocator::construct
            void construct(pointer p, const T& value) {
                _construct(p, value);
            }
    
            // allocator::destroy
            void destroy(pointer p) { _destroy(p); }
    
            // pointer allocator::address
            pointer address(reference x) { return (pointer)&x; }
    
            // const_pointer allocator::const_address
            const_pointer const_address(const_reference x) {
                return (const_pointer)&x;
            }
    
            // size_type allocator::max_size()
            size_type max_size() const {
                return size_type(UINT_MAX / sizeof(T));
            }
        };
    }
    
    int main() {
        int ia[5] = { 0, 1, 2, 3, 4 };
        unsigned int i;
    
        std::vector<int, T_::allocator<int>> iv(ia, ia + 5);
        for (i = 0; i < iv.size(); i++) std::cout << iv[i] << ' ';
        std::cout << '\n';
    
        return 0;
    }
    C++

    The STL here is just a simple aloctor.

    2. SGI space configurationr

    The SSI configuration is different from the normal aloctor

    NameSyntax:Accept ParametersCompiler
    GENERALAllocatorvector<int, std::allocator<int>> ivYes.VC or VB
    SGIAllocvector<int, sd::alloc> ivYesGCC OR G++

    Although the GSI aloc is different from the standard alocator, it does not affect its use, as it is generally stated in default.
    Each of SGI's containers uses default space configuror aloc, as follows:

    template<class T, class Alloc = alloc>  // 缺省使用alloc为配置器
    class vector{ ... };
    C++

    2.1 SGI standard space configurer std::allocator

    There's a name inside SGI.allocatorIt's less efficient than a standard space configuror.C++Yes. ::operator new and ::operator delete Simple packaging was performed.
    This is SGI_style's.allocatorAchieved.
    Note: SGI_styleallocatorAccept onlyvoid*type.

    #include <new>
    #include <cstddef>
    #include <cstdlib>
    #include <limits>
    #include <iostream>
    #include <algorithm>
    #include <vector>
    
    /*-----generalized_style-----*/
    template<class T>
    inline T* allocate(ptrdiff_t size, T*)
    {
        std::set_new_handler(0);
        T* tmp = (T*)(::operator new ((size_t)(size * sizeof(T))));
        if(tmp == 0)
        {
            std::cerr << "out of memory" << '\n';
            exit(1);
        }
        return tmp;
    }
    
    template<class T>
    inline void deallocate(T* buffer)
    {
        ::operator delete(buffer);
    }
    
    /*-----SGI_style-----*/
    template<class T>
    class allocator
    {
    public:
        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;
        typedef ptrdiff_t	difference_type;
    
        pointer allocate(size_type n)
        {
            return ::allocate((difference_type)n, (pointer)0);
        }
    
        /*-----in the book-----*/
        /*void deallocate(pointer p) { ::deallocate(p); }
          this deallocate is not runnable */
        /*-----and the below "deallocate" is runnable --------------*/
    
        void deallocate(pointer p, size_type n) { ::deallocate(p); }
        pointer address(reference x) { return (pointer)&x; }
    
        const_pointer const_address(const_reference x)
        {
            return (const_pointer)&x;
        }
    
        size_type init_page_size()
        {
            return std::max(size_type(1), size_type(4096 / sizeof(T)));
        }
    
        size_type max_size() const
        {
            return std::max(size_type(1), size_type(UINT_MAX / sizeof(T)));
        }
    };
    
    int main() {
        int ia[5] = { 0, 1, 2, 3, 4 };
        unsigned int i;
    
        std::vector<int, allocator<int>> iv(ia, ia + 5);
        for (i = 0; i < iv.size(); i++) std::cout << iv[i] << ' ';
        std::cout << '\n';
    
        return 0;
    }
    C++

    2.2 SGI Special Space Configurer std::alloc

    SGI_style.std::allocCompared to SGI_style std::allocatorIn contrast, the former is allocated/released to grass-roots memory.operator::new & operator::delete Efficiency optimization.

    Operations in C++ for the configuration and release of the object to memory

    class Foo { ... };
    Foo* pf = new Foo;  // allocate memory, then construct object
    delete pf;          // destruct object, and release memory

    The process for memory configuration and release is as follows:

    Phase IPhase II
    Oh, new.Call::operator newConfigure MemoryCallFoo::Foo()Construct Object
    DeleteCallFoo::~Foo()Parsing ObjectsCall::operator deleteRelease Memory

    In short:

    1. Build a house. ::operator new
    2. People live inside. Foo::Foo()
    3. He's gone. Foo::~Foo()
    4. I'll tear down the house. ::operator delete

    In STL allocator there is a fine division of the steps above:

    1. alloc::allocate() Configure Space
    2. alloc::deallocate() Release Space
    3. alloc::construct() Construct Object
    4. alloc::destroy() Parsing Objects

    In STL standard, allocator defines<memory>Medium, SGI<memory>contains the following two documents:

    #include<stl_alloc.h>     // for allocating and deallocating of memory
    #include<stl_construct.h> // for constructing and destructing of object
    C++

    2.3 Basic tools for construction and characterization: construct() & destroy()

    Image_19.png

    Code the contents of the above diagram:

    I'm sorry, I'm sorry.

    #include <new>          // for placement new
    
    template<class T1, class T2>
    inline void construct(T1* p, const T2& value){
        new(p) T1(value);   // placement new -> invoke T1::T1(value);
    }
    C++

    First editiondestroy()Specified

    template<class T>
    inline void destroy(T* pointer){
        pointer->~T();      // invoke ~T()
    }
    C++

    pointer->~T();General resolution.

    Second editiondestroy()I don't know.

    template<class T>
    inline void destroy(ForwardIterator first, ForwardIterator last){
        __destroy(first, last, value_type(first));
    }
    C++

    __destroy(first, last, value_type(first));Select the most appropriate way to structure according to the type of element.

    Second editiondestroy()SE

    • SE1 (char* ver.) -> inline void destroy(char*, char*) {}
    • SE2 (wchar_t*ver.) -> inline void destroy(wchar_t*, wchar_t*) {}

    To determine whether the type of element existstrivial destructorI don't know.

    template<class ForwardIterator, class T>
    inline void __destroy(ForwardIterator first, ForwardIterator last, T*){
        typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
        __destroy_aux(first, last, trivial_destructor());
    }
    C++
    • Element type:non-trivial destructorSpecified
    templace<class ForwardIterator>
    inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type){
        for(; first < last; ++first) destroy(&*first);
    }
    C++
    • Element type:trivial destructorSpecified
    template<class ForwardIterator>
    inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type){}
    C++

    Supplementary and explanatory information:

    1. The functions used to construct and interpret are global functions (STL Regulation: Configurers must have two member functions called Constract() and Destroy())
    2. Up there.construct()Accept one Pointerp And one. Original value- ♪ Set the beginning to the pointer ♪ placement new()
    3. First editiondestroy()Accepts a pointer to deconstruct the object to which the pointer refers. Second editiondestroy()AcceptfirstandlastTwo intertrajectors for deconstructing all objects within [first, last]*
    4. Before an object is convulsed, consider whether all the convulsive functions of the object are availableI don't know.-> value_type() - > __type_traits<T> (Assumption of the contours of this type of objectI don't know.)
      4.1 Yes(__true_type) , directly ignore
      4.2 No(__false_type) , each subject in the rangeDestroy() (first edition)

    2.4 Space allocation and release std::alloc

    Generally, an object is configured in space before it is constructed, and after the object is constructed, the configured memory is released. In SGI, the processes are as follows:

    • General I'm sorry. Request space
    • Consider Multi-SystemMulti-threads ) status
    • Response measures when inadequate memory is considered
    • Consideration of potential memory debris problems that may arise from memory distribution

    Memory debris: Different object configurations require different sizes of memory space to be requested, but may result in small memory blocks (part of which does not meet the memory needs of the target object ' s construction) that are not reasonably distributed (with small or large) and that are increasingly available over time, even if the total remaining memory is large enough to allocate space to a large object, thus causing waste of memory.

    Possible problems caused by memory debris:

    1. Available space for fragmentation
    2. Inefficient use of memory
    3. Increased probability of distribution failure
    4. Performance is down.

    Possible solutions to the problem of memory debris:

    1. Use a fixed size memory pool: pre-allocated a fixed size memory block
    2. Custom memory profiler: memory distribution according to actual needs
    3. Memory compression: manual removal of memory debris
    4. Smart Memory Management: RAII and Smart Pointer
    5. Limiting dynamic memory allocation: use of stack-based memory allocation for small, short-life objects and dynamic memory allocation for large, long-life objects

    In C++, configure memory is::operator new() The release of memory is::operator delete()
    Category Cmalloc()andfree()function.
    It's also passed in the SGI.malloc()andfree()Two functions to configure and release memory.

    Taking into account the problem of memory debris mentioned above, SGI designed a two-tiered memory configuration:

    #ifdef
    typedef __malloc_alloc_template<0> malloc_alloc;
    typedef malloc_alloc alloc;     // set alloc as primary allocator
    #else
    // set alloc as secondary allocator
    typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
    #endif
    C++
    • The first stage configuration is directly usedmalloc()andfree()
    • The second-tier configurationr uses different distribution strategies, depending on the situation:
      • Use level 1 configuration when RAM blocks greater than 128bytes need to be configured
      • Use secondary configuration - > I don't know. (RAMS)

    SGI will seal a two-layer and two-layer configuration with one of the following interfaces (to make it STL-compliant):

    template<class T, class Alloc>
    class simple_alloc{
    public:
        static T* allocate(size_t n){
            return 0 == n ? 0 : (T*) Alloc::allocate(n * sizeof(T));
        }
        static T* allocate(void){
            return (T*) Alloc::allocate(sizeof(T));
        }
        static void deallocate(T* p, size_t n){
            if(0 != n) Alloc::deallocate(p, n * sizeof(T));
        }
        static void deallocate(T* p){
            Alloc::deallocate(p, sizeof(T));
        }
    };
    C++

    In summary, the member functions within the interface are those within the configuration, where the configuration unit is the size of the target elementsizeof(T)

    All SGI STL containers are used.simple_allocThis interface, such as:

    template<class T, class Alloc = alloc> // default
    class vector{
    protected:
        // specified allocator that allocates a size of element per time
        typedef simple_alloc<value_type, Alloc> data_allocator;
        void deallocate(){
            if(...) data_allocator(start, end_of_storage - start);
        }
        ...
    };
    C++

    Map of the relationship of the first and second configurations

    Image_20.png

    Map of the relationship between the first and second configurations and the packaging interface and mode of operation

    Image_21.png

    2.5 First stage configuration _ Malloc_alloc_template Parsing

    #if 0
    #   include <new>
    #   define THROW_BAD_ALLOC throw bad_alloc
    #elif #defined(__THROW_BAD_ALLOC)
    #   include <iostream>
    #   define __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1);
    #endif
    C++

    特点:

    • Slower than default allocator
    • It's generally thread-safe, with higher utilization of memory space

    Notes:

    1. No reference template usage

    template<>is defined as the internal type and variable name, which is given directly to the variable at the time of the demonstration.

    template<int inst>
    class ex{
    public:
      void printf() { std::cout << "inst: " << inst; }
    };
    
    int main(){
      /* ex<para> e1; here para = inst */
      ex<3> e1;
      e1.printf();
      return 0;
    }
    C++
    1. staticModifier
    staticNon-static
    I don't know.AccessibleAccessible
    I don't want to see you again, but...AccessibleI'm sorry.
    ♪ This ♪I'm sorry.Accessible
    #include <iostream>
    
    class MyClass{
    public:
        static void staticFunction(){
            std::cout << "Inside staticFunction" << '\n';
        }
    
        void nonstaticFunction(){
            std::cout << "Inside nonstaticFunction" << '\n';
        }
    };
    
    int main(){
        /* call static function without an instance */
        MyClass::staticFunction();
    
        /* class static function from an instance (this is uncommon but valid) */
        MyClass obj;
        obj.staticFunction();
    
        /* cannot call non-static function without an instance */
        MyClass::nonStaticFunction(); /* Error cannot call non-static function
                                         without instance */
    
        /* call non-static function from an instance */
        obj.nonstaticFunction();
    
        return 0;
    }
    C++
    1. Function Pointer Usage
    #include <iostream>
    
    int add(int a, int b);
    int subtract(int a, int b);
    
    int main(){
        int(*operation)(int, int);  /* declaration of function pointer */
    
        operation = add;    /* assign address of add function to operation */
        printf("Addition result: %d\n", (*operation)(5, 3));    /* call function through function pointer */
    
        operation = subtract;   /* assign address of subtract function to operation */
        printf("Subtract result: %d\n", (*operation)(5, 3));    /* call function through function pointer */
    
        return 0;
    }
    
    int add(int a, int b){ return a + b; }
    int subtract(int a, int b) { return a - b; }
    C++

    Level 1 Configurer

    template<int inst>
    class __malloc_alloc_template {
    private:
        /* the below are the function
         pointers which are used for cope with the out-of-memory(oom) */
        static void *oom_malloc(size_t);
    
        static void *oom_realloc(void *, size_t);
    
        static void (* __malloc_alloc_oom_handler)();
    
    public:
        /* allocator */
        static void *allocate(size_t n) {
            void *result = malloc(n); // primary allocator employs malloc() directly
            /* when out of memory, invoke oom_malloc() */
            if (0 == result) result = oom_malloc(n);
            return result;
        }
    
        /* deallocator */
        static void deallocate(void *p, size_t /* n */) {
            free(p);  /* primary allocator employs free() directly */
        }
    
        /* reallocator */
        static void *reallocate(void *p, size_t /* old_sz */, size_t new_sz) {
            void *result = realloc(p, new_sz);
            /* primary allocator employs realloc() directly
     when out of memory, invoke oom_realloc()*/
            if (0 == result) result = oom_realloc(p, new_sz);
            return result;
        }
    
        /* emulate set_new_handler(), which means that
           out-of-memory handler is definable */
        static void (*set_malloc_handler(void (*f)()))() {
            void (* old)() = __malloc_alloc_oom_handler;
            __malloc_alloc_oom_handler = f;
            return (old);
        }
    };
    
    /* malloc_alloc out-of-memory banding */
    template<int inst>
    void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;
    
    template<int inst>
    void* __malloc_alloc_template<inst>::oom_malloc(size_t n){
      void (* my_malloc_handler)();
      void* result;
    
      for(;;){ /* endlessly allocate and deallocate until allocate successfully */
        my_malloc_handler = __malloc_alloc_oom_handler;
        if(0 == my_malloc_handler) { __THROW_BAD_ALLOC; }
        (* my_malloc_handler)();
        result = malloc(n);
        if(result) return result;
       }
    }
    C++

    Notes: typedef __malloc_alloc_template<0> malloc_alloc;
    Here.instThe value is designated zero.

    The first-stage configuration is mainly used C Mediummalloc()free()realloc()function to configure, release, reconfigure and imitate memory C++ Yes.new-handlerMechanisms (because they are not directly used here) ::operator newConfigure memory)

    C++ Medium new handler : When the system is unable to meet memory configuration needs, you can call a function you specify.
    In other words, when operator::new When the memory configuration operation cannot be completed, a self-defined function is called before dropping an anomaly new-handler It is a compromise to solve the problem of memory deficiencies.

    Notes:

    1. SGIMedium malloc Not ::operator new To configure memory, a similar need needs to be achieved C++ Medium set_new_handler() function set_malloc_handler()
    2. SGI's first-degree configuration. allocate() and realloc() It's all on call. malloc() and realloc() Could not close temporary folder: %s oom_malloc() and oom_realloc() , and then try to recycle and release memory until memory configuration is successful, if memory configuration is not successful, then oom_malloc() and oom_realloc() It'll throw out.__THROW_BAD_ALLOC Yes. Bad_allocUnusual information, or direct exit(1) Suspension of proceedings.
    3. The processing function for memory deficiencies is performed by the programme designer.

    2.6 Second stage configuration_default_alloc_templateParsing

    The secondary configurationr has additional mechanisms than the first-stage configuration, which reduces the amount of memory waste (RAM debris) associated with small memory distributions and makes subsequent memory space management more difficult because the system always relies on this additional space for memory management.

    Image_22.png

    Memory distribution mechanism for the SGI-II configurationr:

    • When the assigned memory blocks are large enough (greater than 128bytes), they are transferred to the first-stage configuration.
    • When the amount of memory blocks allocated is smaller (less than 128bytes), it is managed through memory pool - sub-level management

    Level approach
    Each time you configure a large memory and maintain the corresponding free-lists. Next time you have the same size memory needs, distribute them directly from the free-lists, and if the program releases small memory blocks, then recycle them from the configuration to the free-lists.

    In order to facilitate management, SGI ' s secondary configuration will proactively increase any small memory requirements up to a multiple of 8 and maintain 16 free-lists of 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128 bytes.

    free-lists The structure is as follows:

    union obj{
      union obj* free_list_link;
      char client_data[1];
    };
    C++
    Image_23.png

    Here, union obj* free_list_link Comparable to chain LinkList* next, char client_data[1]In turn, it points to actual memory blocks, which saves the costs of memory.

    Add:

    • structEach member variable has its own memory space.
    • unionEach member variable shares a memory space

    Part of the secondary configuration

    #include <cstddef>
    
    enum {__ALIGN = 8};
    enum {__MAX_BYTES = 128};
    enum {__NFREELISTS = __MAX_BYTES / __ALIGN };
    
    template<bool threads, int inst>
    class __default_alloc_template{
    private:
        static size_t ROUND_UP(size_t bytes){ return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1)); }
        /*  '(bytes + __ALIGN -1': This part adds an offset to the 'bytes' value. The '__ALIGN - 1'
         *  is used to ensure that we round up to the next multiple of '__ALIGN'.For example, if '__ALIGN'
         *  is 4, then '__ALIGN - 1' is 3, and adding 3 ensures that the result will be at least a multiple
         *  of 4 greater than 'bytes'.
         *  '& !(__ALIGN - 1)‘: This part performs a bitwise AND operation wit the complement of '(__ALIGN - 4)'.
         *  The complement operation '~' flips all the bits of '(__ALIGN - 1)', effective creating a bitmask where
         *  all bits are set to 1 except for the lower bits determined by '__ALIGN - 1'. By performing a bitwise AND
         *  with this bitmask, we effectively set the lower bits to O, thus rounding down the result to the nearest
         *  multiple of '__ALIGN'.
         */
    private:
        union obj{
            union obj* free_list_link;
            char client_data[1];
        };
    private:
        /* 16 free-lists */
        static obj* volatile free_list[__NFREELISTS];
        /* employ Nth free-list according to the size
         * of memory chunk (begin with 1st memory chunk)
        */
        static size_t FREELIST_INDEX(size_t bytes){ return (((bytes) + __ALIGN + 1) / __ALIGN - 1); }
        /* return a object in size of n, and add other memory
         * chunks in size of n into free-list if possible
        */
        static void* refill(size_t n);
        /* allocate a memory space that can hold n objs
         * its capacity is size
         * if inconvenient to allocate for n objs, n cloud decrease
        */
        static void* refill(size_t size, int& nobjs);
    
        // chunk allocation state
        static char* start_free;    // the start position in memory pool, only changes in chunk_alloc()
        static char* end_free;      // the end position in memory pool, only changes in chunk_alloc()
        static size_t heap_size;
    public:
        static void* allocate(size_t n) { }
        static void deallocate(void* p, size_t n) { }
        static void* reallocate(void* p, size_t old_sz, size_t new_sz);
    };
    
    /* the original value and definition setting for static data member */
    template <bool threads, int inst>
    char* __default_alloc_template<threads, inst>::start_free = 0;
    
    template <bool threads, int inst>
    char* __default_alloc_template<threads, inst>::end_free = 0;
    
    template <bool threads, int inst>
    size_t __default_alloc_template<threads, inst>::heap_size = 0;
    
    template <bool threads, int inst>
    __default_alloc_template<threads, inst>::obj* volatile
    __default_alloc_template<threads, inst>::free_list[__NFREELISTS] =
            {0, 0, 0, 0, 0, 0, 0, 0 , 0, 0, 0, 0, 0, 0, 0, 0};
    C++

    2.7 Space Configuration Functionsallocate()

    Configurer ' s standard interface function alocate() works as follows:

    • judge the size of blocks
      • More than 128bytes Call Level 1 Configurer
      • Less than 128bytes check the corresponding free list
        • If there are available blocks in free list, use them directly.
        • If there are no available blocks in the free list, increase the size of the block to eight times bytes and call refill() to refill the space for free list
    // n must be > 0, to assure that
    // there is the memory allocated
    static void* allocate(size_t n){
      obj* volatile* my_free_list;
      obj* result;
    
    // if allocating memory > 128 bytes
    if(n > (size_t) __MAX_BYTES){
      return (malloc_alloc:allocate(n));
    }
    
    // select a suitable chunk in 1 of 16 free lists
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if (result == 0){
      // no find a available free list
      // preparing to refill another
      void* r = refill(ROUND_UP(n));
      return r;
    }
    // adjust free list
    *my_free_list = result -> free_list_link;
    return (result);
    };
    C++

    Operation where memory blocks are removed from free list

    Image_25.png

    2.8 Space Release Functiondeallocate()

    __default_alloc_template has a standard interface function deallocate() which works as follows:

    • judge the size of blocks
      • More than 128bytes, direct to first-level configuration
      • Less than 128bytes, find the corresponding free list and recycle blocks
    // p can not be 0
    static void deallocate(void* p, size_t n){
        obj* q = (obj *)p;
        obj* volatile* my_free_list;
    
        // if size > 128 bytes, call the primary allocator
        if(n > (size_t) __MAX_BYTES){
            malloc_alloc::deallocate(p, n);
            return;
        }
    
        // selecting the corresponding free list
        my_free_list = free_list + FREELIST_INDEX(n);
    
        // adjust free list, recycle the chunk
        q -> free_list_link = *my_free_list;
        *my_free_list = q;
    }
    C++

    Memory block recovery to free list

    Image_26.png

    2.9 Refillfree lists

    When free lists do not have available blocks, call refill() as free list refill space, new space removed from memory pool (completed by chunk_alloc()):

    • When the remaining memory in the memory pool was sufficient, 20 new blocks were acquired by default
    • When the remaining memory in the memory pool is insufficient, the number of new blocks obtained may be less than 20
    // return a obj in size of n
    // and supply some chunks for free list properly sometimes
    // supposing that n is upregulated to a multiple of 8
    template <bool threads, int inst>
    void* __default_alloc_template<threads, inst>::refill(size_t n){
        int nobjs = 20;
        // call chunk_alloc(), try to get n(objs) chunks
        // as new nodes for free list
        char* chunk = chunk_alloc(n, nobjs); // nobjs -> pass by reference
        obj* volatile* my_free_list;
        obj* result;
        obj* current_obj, * next_obj;
        int i;
    
        // if only get one chunk, then allocate this chunk to the target
        // so there is no new node for free list
        if(l == nobjs) return(chunk);
        // if not, adjust free list and prepare to link a new node
        my_free_list = free_list + FREELIST_INDEX(n);
    
        // create free list in chunk space
        result = (obj*)chunk;   // this piece of chunk will return to user
        // help free list point toward the new allocated space (from memory pool)
        *my_free_list = new_obj = (obj*)(chunk + n);
        // connect the each node in free list
        for(i = 1; ; i++){      // i begin with 1, because i[0] will be return to user
            current_obj = next_obj;
            if (nobjs - 1 == i){
                current_obj -> free_list_link = 0;
                break;
            }else{
                current_obj -> free_list_link = next_obj;
            }
        }
        return(result);
    }
    C++

    2.10 Memory poolmemory pool

    Speaking of...refill(). . . . . . . . .chunk_alloc()This function, it's used to extract space from the memory pool to giveFree list

    template <bool threads, int inst>
    char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs){
        char* result;
        size_t total_bytes = size * nobjs;
        size_t bytes_left = end_free - start_free;  // the rest of memory pool
    
        if(bytes_left >= total_bytes){
            // the rest of memory pool could satisfy the need
            result = start_free;
            start_free += total_bytes;
            return(result);
        }else if( bytes_left >= size){
            // the rest of memory pool could not totally satisfy the need
            // but only one or more chunks
            nobjs = bytes_left / size;
            total_bytes = size * nobjs;
            result = start_free;
            start_free += total_bytes;
            return(result);
        }else{
            // memory pool can not supply the size o only one chunk
            size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
            // make full use of memory fragments
            if(bytes_left > 0){
                // allocate some surplus memory for free list primarily
                // select the proper free list
                obj* volatile* my_free_list = free_list + FREELIST_INDEX(bytes_left);
                // adjust free list, integrate the surplus memory in memory pool
                ((obj*) start_free) -> free_list_link = *my_free_list;
                *my_free_list = (obj*) start_free;
            }
    
            // configure heap to supply memory pool
            start_free = (char*) malloc(bytes_to_get);
            if(0 == start_free){
                // heap is not enough, malloc() fails
                int i;
                obj* volatile* my_free_list, *p;
                // check the memory that I currently have
                // allocate the small memory space is dangerous under multi-threads circumstance
                // select a proper free list
                // "proper" means that the unused memory is big enough to satisfy the need
                for(i = size; i <= __MAX_BYTES; i += __ALIGN){
                    my_free_list = free_list + FREELIST_INDEX(i);
                    p = *my_free_list;
                    if(0 != p){
                        // chunk not in free list
                        // adjust free list to release the unused memory
                        *my_free_list = p -> free_list_link;
                        start_free = (char*) p;
                        end_free = start_free + i;
                        // revoke itself to fix nobjs
                        return(chunk_alloc(size, nobjs));
                        // any surplus memory will be integrated in free list as spare
                    }
                }
                end_free = 0;   // if occur the accident, allocate the primary allocator
                // turning to out-of-memory
                start_free = (char*)malloc_alloc::allocate(bytes_to_get);
                // if failed, throw exception
                // if succeeded, memory lack will be fixed
            }
         heap_size += bytes_to_get;
            end_free = start_free + bytes_to_get;
            // revoke itself to fix nobjs
            return(chunk_alloc(size, nobjs));
        }
    C++

    chunk_alloc()Function passesend_free - start_freeTo determine the memory balance in memory:

    • If there's enough memory, we'll move 20 blocks back.Free list
    • If memory is insufficient
      • But if one or more blocks are satisfied, then the remaining blocks will be transferred.
      • I can't even move a block out, and I'm trying to use it.malloc()FromHeapMiddle Configure Memory to Increase Memory Residues in Memory Pools, and the size of the Configuration Memory has gradually increased with the number of configurations

    Examples:

    • When Callchunk_alloc(32, 20)♪ And then ♪malloc()Configure 40 32bytes blocks, remove the first of these blocks and release the remaining 19free_list[3]Carry out maintenance and leave the remaining 20 to the memory pool.
    • When called againchunk_alloc(64, 20)Time, now.free_list[7]Empty, and 20 * 32 bytes / 64 bytes = 10, return the 10 blocks, remove the first of them and release the remaining nine.free_list[7]Central maintenance. The memory pool is empty at this time.
    • Keep calling.chunk_alloc(96, 20)At this point,free_list[11]It's empty and it's time to look for memory from the memory pool, but it's also empty, so there'smalloc()Configure 40 + n (additional) 96bytes blocks, remove the first block of the block and hand 19 tofree_list[11]Maintain the remaining 20+n (additional) to the memory pool ...
    • ♪ When the wholeI'm sorry.There's so little space to add memory to the memory pool.malloc()It is not impossible to continue.chunk_alloc()Again.Free listFinds if there are available and large enough blocks, if available, or if there is no first-level configuration, use the first-level configuration.malloc()Make memory configuration, but it hasOut-of-memoryTreatment mechanism (similar tonew handler):
      • If successful, release enough memory to use
      • If you fail, throw.bad_allocUnusual
    Image_27.png

    simple_alloc to provide a standard configuration interface

    template<class T, class Alloc>
    class simple_alloc{
    public:
        static T* allocate(size_t n){
            return 0 == n ? 0 : (T*) Alloc::allocate(n * sizeof(T));
        }
        static T* allocate(void){
            return (T*) Alloc::allocate(sizeof(T));
        }
        static void deallocate(T* p, size_t n){
            if(0 != n) Alloc::deallocate(p, n * sizeof(T));
        }
        static void deallocate(T* p){
            Alloc::deallocate(p, sizeof(T));
        }
    };
    C++

    SGI usually uses this way to use the configuration:

    template <class T, class Alloc = alloc> // 缺省使用 alloc 为配置器
    class vector{
        public:
            typedef T value_type;
        protected:  // 专属配置器,每次配置一个元素大小
            typedef simple_alloc<value_type, Alloc> data_allocator;
    };
    C++

    2.11 Memory Basic Processing Tool

    STL defines five global functions that function in uninitialized spaces:

    1. construct()- > Construct
    2. destroy()- I'm sorry.
    3. uninitialized_copy()-> copy()
    4. uninitialized_fill()-> fill()
    5. uninitialized_fill_n()-> fill_n()

    of which copy()fill()fill_n() ForSTLAlgebra

    uninitialized_copy()

    template<class InputIterator, class ForwardIterator>
    ForwardIterator
    unitialized_copy(InputIterator first, InputIterator last,
    ForwardIterator result);
    C++

    uninitialized_copy()Dissociate the memory configuration from the structure of the object, working in the working mode: within the input range, using an iterative devicei , the anecdote will be called construct(&*(result + (i - first)), *i) , which produces*i, and place the object to which the copy is given at the relative position of the output range.
    When it comes to a container, uninitialized_copy()The tectonic function of the packaging over the memory block after it has configured the memory block (in order to include all the elements within it).
    C++ has another requirement for this:I don't know what to do with it. : If it works for every elementuninitialized_copy() , thenI don't know. Or elseRollback

    uninitialized_fill

    template<class ForwardIterator, class T>
    void unintialized_fill(ForwardIterator first, ForwardIterator last,
     const T&x);
    C++

    uninitialized_fill()Dissociate the memory configuration from the structure of the object, working in the same way: for each iterative in the input rangei Call construct(&*i, x) Yes.iPlace a copy of x where it refers.
    C++ has the same thing.I don't know what to do with it.Requirements: If every element can be successfully implementeduninitialized_fill() , thenI don't know. , if there's oneCopy constructorThrows an anomaly (operating failure) and disassembles all successful elements.

    uninitialized_fill_n

    template<class ForwardIterator, class Size, class T>
    ForwardIterator
    uninitialized_fill_n(ForwardIterator first, Size n, const T& x);
    C++

    uninitialized_fill_n()Separates the memory configuration from the structure of the object, which sets the same opening value for all elements in the specified range. Yes.[first, first + n]Every iterative in rangei, uninitialized_fill_n()Callconstruct(&*i, x) It's created in the corresponding position.x.
    C++ has the same thing.I don't know what to do with it.Request: If it succeeds for every elementuninitialized_fill_n() , thenI don't know. , if there's oneCopy constructorThrows an anomaly (operating failure) and disassembles all successful elements.

    Leave a Comment

    Share this Doc

    Chapter II Space configurationr (alloctor)

    Or copy link

    CONTENTS
    Remember to rest.