Memory Allocation of STL containers

Source: Internet
Author: User

This article references Hou Jie's STL source code analysis, so it mainly introduces the sgi stl implementation version. This version is also a built-in version of G ++, and J. the plauger implementation version corresponds to the CL version, which is based on HP. If you are interested, you can refer to the latest source code header file and start with a declaration.

/*** Copyright (c) 1994 * Hewlett-Packard Company (here) ** permission to use, copy, modify, distribute and merge this software * and its documentation for any purpose is hereby granted without tables, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. hewlett-Packard Company makes no * representations about the suitability of this software for any * purpose. it is provided "as is" without express or implied warranty. * ** copyright (c) 1996 * Silicon Graphics Computer Systems, Inc. (and here) ** permission to use, copy, modify, distribute and publish this software * and its documentation for any purpose is hereby granted without handle, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. silicon Graphics makes no * representations about the suitability of this software for any * purpose. it is provided "as is" without express or implied warranty. */

Speaking of memory allocation of STL containers, we must start with new.

We know that when a new object comes out, the compiler will do two steps, which is called "New Operation ":

Allocating memory for this type creates an object of this type in the newly allocated memory. Although the New Keyword is very convenient, it can be predicted that in some application scenarios, the runtime overhead is not ideal, or a better strategy can save the overhead, and the overhead will be paid when necessary.

For example, the vector container corresponds to the arraylist in C # or Java. This container features that a fixed size of memory has been allocated once new containers come out, after several insert element operations, if the space is insufficient, a new memory is requested and the size of the newly applied memory is determined based on the specific policy. Generally, the size is twice the size of the original memory. Of course, this is based on a vector constructor that accepts a size parameter. Let's take a look at the implementation of the default constructor of vector:

const size_type __old_size = size(); const size_type __len = __old_size != 0 ? 2 * __old_size : 1; iterator __new_start = _M_allocate(__len);

Here, _ Len is the size of the new space. It can be seen that it is twice the size of the original space. At the same time, it can be seen that if the size is 0 units, one unit of space will be allocated first, allocated by the m_allocate function, copy the original elements to the newly allocated memory, and return the original space:

Const size_type _ old_size = size (); const size_type _ Len = _ old_size! = 0? 2 * _ old_size: 1; iterator _ new_start = _ m_allocate (_ Len); iterator _ new_finish = _ new_start; _ stl_try {_ new_finish = uninitialized_copy (_ m_start, _ Position, _ new_start); construct (_ new_finish, _ x); ++ _ new_finish; _ new_finish = uninitialized_copy (_ Position, _ m_finish, _ new_finish);} _ stl_unwind (destroy (_ new_start ,__ new_finish ), _ m_deallocate (_ new_start ,__ Len); // The destroy operation calls the destroy (begin (), end () destructor of the object; // begin () inside _ m_start, end () is _ m_finish/_ m_deallocate, which is used to reclaim space _ m_deallocate (_ m_start, _ m_end_of_storage-_ m_start); _ m_start = _ new_start; _ m_finish = _ new_finish; _ m_end_of_storage = _ new_start + _ Len;

Let's look at how many units of memory is allocated by the vector in the new system.

Default vector constructor:

typedef _Vector_base<_Tp, _Alloc> _Base;explicit vector(const allocator_type& __a = allocator_type())    : _Base(__a) {}
VectorDefault base constructor:

typedef _Alloc allocator_type;_Vector_base(const _Alloc&)    : _M_start(0), _M_finish(0), _M_end_of_storage(0) {}

 

We can see that when the vector is new, there is no space.MStart,MFor more information about the significance of variable names, such as finish, refer to relevant books, or ask the search engine.

Well, the following is the source code of the constructor that accepts a size parameter:

explicit vector(size_type __n)    : _Base(__n, allocator_type())    { _M_finish = uninitialized_fill_n(_M_start, __n, _Tp()); }
Looking at the implementation, let's first imagine that once the container is new, there will certainly be a number of elements in the container. If you do not adopt a special response policy, in general, apart from allocating memory, there will also be a series of construction operations. For example, if we define a vector V (N), each bigobject carries a default constructor (hypothesis) that needs to be executed for a long time. However, we only use the space in n/2 units, in addition to n/2 units of idle space (this is impossible, so do not consider disadvantages .), The construction of n/2 unit spaces is meaningless.

As a result, the designer makes use of some features provided by C ++ to solve this problem, that is, separating space allocation from object construction.

Let's take a look at it first.

Construct (T * P, var t): Creates a new element in the memory referred to by P, where T is used for replication initialization.

Destroy (T * P): Opposite to construct, run the destructor of the object referred to by P.

Note that both require P to point to a memory.

Placement new(Locate new): syntax form: New (p) type (VAL ). Different from the traditional new method, the system locates new and only constructs objects in the allocated memory, not allocating memory.

We can guess that the internal implementation of construct may be to locate new, but sometimes it is not necessarily true... But... This is actually correct:

void construct(pointer p, const Tp& val) { new(p) Tp(val); }
However, you may not think that destroy runs the Destructor internally... Well, you are right:

void destroy(pointer p) { _p->~Tp(); }
(Hey, you have already exposed a code snippet ~) Sometimes the world is full of malware.

After knowing these things, actually, STL does nothing mysterious about memory allocation.

Now that we know how to save the overhead of the vector and how to use the new feature in the construction, is the memory allocation using operator new? Think about it. This is because it is actually used for memory allocation, but it is not constructed. Therefore, an element generated in the vector is actually operator New + placement new! Well, but the source code has given us a relentless blow. We have seen that in the implementation of vector, memory allocation relies onMAllocate implementation, then,MIn allocate _, how does it happen? Actually:

_Tp* _M_allocate(size_t __n)    { return _M_data_allocator.allocate(__n); }///////////////////////////////////////////////typedef simple_alloc<_Tp, _Alloc> _M_data_allocator;//////////////////////////////////////////////template<class _Tp, class _Alloc>class simple_alloc {public:    static _Tp* allocate(size_t __n)      { return 0 == __n ? 0 : (_Tp*) _Alloc::allocate(__n * sizeof (_Tp)); }    static _Tp* allocate(void)      { return (_Tp*) _Alloc::allocate(sizeof (_Tp)); }    static void deallocate(_Tp* __p, size_t __n)      { if (0 != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp)); }    static void deallocate(_Tp* __p)      { _Alloc::deallocate(__p, sizeof (_Tp)); }};
As you can see, it uses a packaging class called simple_alloc internally.

So far, we can't continue to pursue it because it will involve different memory allocation policies based on different needs. In fact, there are two allocation policies in the SLT.

Level 1 configurator: Default policy. I will talk about Level 2 configurator later: The cause is to avoid memory fragmentation caused by too many small blocks (in fact, it is still to be studied in depth here, so far, the latest version of STL source code has such a sentence: with a reasonable compiler, this (refers to the first-level configurator) shocould be roughly as fast as the original STL class-specific allocators, but with less fragmentation .), the memory pool is used for management and will not be discussed in depth. First, I did not understand the internal implementation of this policy, so I was afraid I could not hold it. The second reason is:

DefaultAllocTemplate parameters are experimental and may disappear in the future. Clients shocould just use alloc for now.

Well, the authors have all said this.

However, what is the first-level configurator? It is called_ MallocAlloc_template, how does it allocate memory? See:

static void* allocate(size_t __n) { void* __result = malloc(__n); if (0 == __result) __result = _S_oom_malloc(__n); return __result; }

The code above comes from the internal implementation of mallocalloctemplate. As you can see, it is actually the malloc function used in the C language for heap memory allocation (the soom_malloc in the second line is used to handle situations where memory allocation fails, do not explain ),

As for why I chose it, I guess it's because of efficiency. I haven't verified it.

So far, we can't think that the vector is internally allocated in this way. We also need to study it. Let's go back to the vector to see if this policy is used.

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >class vector : protected _Vector_base<_Tp, _Alloc> {  // requirements:

As you can see, if the "Allocation Policy" is not specified for the vector, the Default policy is stldefaultallocator. Here we get the answer. After a small script that traverses all files to search for strings, finally, the organization is located in stlconfig. h

# ifndef __STL_DEFAULT_ALLOCATOR#   ifdef __STL_USE_STD_ALLOCATORS#     define __STL_DEFAULT_ALLOCATOR(T) allocator< T >#   else#     define __STL_DEFAULT_ALLOCATOR(T) alloc#   endif# endif
We can see that the pre-compiled command here, the official explanation is

_ STLUseSGIAllocators is a hook so that users can disable new-style allocators, and continue to use the same kind of allocators as before, without having to edit library headers.

In addition, stlvector also provides two versions of vectorbase based on whether to define stlusestdallocators. The difference in memory allocation is only the standard interface, one is a self-defined interface (but tracing its source code is still using the standard interface. If you are interested, you can check it out), but we don't have to worry about the difference between them.

Now we need to figure out what alloc is and why not discuss allocator. What about it? Because it is still alloc internally. In stl_alloc, we can see that

# ifdef __USE_MALLOCtypedef malloc_alloc alloc;typedef malloc_alloc single_client_alloc;# else

Oh, oh, mallocalloc, that's you! Well, my mind is lost here, and my thoughts cannot be pulled back. I don't know what I am writing. If you find some problems in my article organization, it doesn't matter, I don't want to listen. (/# = Dish =)/| _ |

PS: By the way, why do we only talk about vector? Because its internal implementation is fixed and representative, although the chain table implementation (such as list) is separate from the allocation and construction, it is still logically the same, so there may be no need to talk about it. Knowing allocator is about to know the memory allocation of the container, the main content is not in the container itself.

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.