Automatic Memory Management of the STL Containers

Contents[Show]

One of the big advantages of C++ string to a C string and of a std::vector to a C arrays is it that both C++ containers automatically manage their memory. Of course, that holds true for all further containers of the Standard Template Library. In this post, I will have a closer look at the automatic memory management of std::vector and std::string.

 

From the user perspective a std::string in C++11 feels like a std::vector. That is the simple reason, I can present them in parallel. Therefore, it fits very well that std::string and std::vector are the most important containers in C++.

std::string and std::vector

The C++ runtime automatically adjusts the size of a std::string and std::vector to its number of elements. And with C++11 in both directions.

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// stringAndVector.cpp

#include <iostream>
#include <string>
#include <vector>

template <typename T>
void showInfo(const T& t,const std::string& name){

  std::cout << name << " t.size(): " << t.size() << std::endl;
  std::cout << name << " t.capacity(): " << t.capacity() << std::endl;

}

int main(){
    
  std::cout << std::endl;

  std::string str;
  std::vector<int> vec;
  
  std::cout << "Maximal size: " << std::endl;
  std::cout << "str.max_size(): " << str.max_size() << std::endl;
  std::cout << "vec.max_size(): " << vec.max_size() << std::endl;
  std::cout << std::endl;
  
  std::cout << "Empty string and vector: " << std::endl;
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;
  
  
  std::cout << "Initialized with five values: " << std::endl;
  str= {"12345"};
  vec= {1,2,3,4,5};
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;
  
  std::cout << "Added four additional values: " << std::endl;
  str += "6789";
  vec.insert(vec.end(),{6,7,8,9});
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;
  
  
  std::cout << "Resized to 30 values: " << std::endl;
  str.resize(30);
  vec.resize(30);
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;

  std::cout << "Reserved space for at least 1000 values: " << std::endl;
  str.reserve(1000);
  vec.reserve(1000);
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;
  
  std::cout << "Shrinked to the current size: " << std::endl;
  str.shrink_to_fit();
  vec.shrink_to_fit();
  showInfo(str,"String");
  showInfo(vec,"Vector");
  std::cout << std::endl;

}

 

The program is quite easy to get. That was my first thought. But wait a second.

To spare typing I wrote the small function showInfo (line 7 - 13). This function returns for a container its size (line 10) and its capacity (line 11). The size of a container is its number of elements, the capacity of a container is the number of elements a container can hold without an additional allocation. Therefore, the capacity of container has at least to be as big as its size. You can adjust the size of a container with its method resize (line 49 and 50); you can adjust the capacity of a container with its method reserve (line 56 and 57).

But, back to the program from top to bottom. I create in line 19 and 20 an empty string and an empty vector. Afterwards, the program displays the maximum numbers of elements a string or vector can have. After each operation on the both container it returns their size and capacity. That holds true for the initialization of the containers (line 34 and 35), for the addition of four new elements (line 42 and 43), the resizing of the containers to 30 elements (line 49 and 50) and the reserving of additional memory for at least 1000 elements (line 56 and 57). With C++11, you can shrink with the method shrink_to_fit (line 63 and 64) the container's capacity to its size.

Before I present the output of the program on Linux and Windows let me make a few observations.

  1. The adjustment of the size and the capacity of the container is done automatically. I haven't used any kind of memory operations like new and delete.
  2. std::string and std::vector support the same interface. The only exception to this rule is line 41. Here I added a C string to a C++ string.
  3. By using the method cont.resize(n) the container cont will get new default-initialized elements, if n > cont.size() is true.
  4. By using the method cont.reserve(n) the container cont will be get new memory for at least n elements, if n > cont.capacity() is true.
  5. The call shrink_to_fit is non-binding. That means, the C++ runtime has not to adjust the capacity of a container to its size. But, my usages of the method shrink_to_fit with GCC, clang, or cl.exe always freed the unnecessary memory.

Here is the output of the program.

 

stringAndVectorstringAndVectorWin

My little astonishment

The program shows, that the MSVC 15 STL implementation is a little bit more greedy than the GCC 4.8 STL implementation. That holds in particular true for std::string. Therefore, the empty std::string has 15 elements. But I was more astonished by the fact that the maximum size of a std::string is as big as the maximum size of a std::vector<int> on Linux. This is astonishing because a int is four times as big as a char on Linux and Windows.

#include <iostream>

int main(){
    
  std::cout << std::endl;

  std::cout << "sizeof(char): " << sizeof(char) << std::endl;
  std::cout << "sizeof(int): " << sizeof(int) << std::endl;
  
  std::cout << std::endl;

}

 

sizeofsizeofWin

 

You have to interpret both values as maximum values. Any ideas to the discrepancy?

My astonishment has disappeared

Thanks to the help of Mark Abraham and Clément Gregoire, the riddle is solved.

Microsoft implementation is more greedy

Microsoft Visuals std::string implementation uses internally small string optimization. Therefore, a small string needs no heap allocation and goes directly to the stack. The boundary is exactly 15 characters. GCC will get a conformant string implementation with 5.1. But I used GCC 4.8 in my test.

GCC with conformant std::string implementation

 If I use a GCC 5.3 with a conformant std::string implementation, the picture will be different.

 

stringAndVectorGCC5 3

To use the conformant std::string implementation in GCC 5.3,  I have to use the compiler flag -D_GLIBCXX_USE_CXX11_ABI=1.Now, the maximum size of std::string is two times the maximum size of std::vector<int>. The C++11 standard says about max_size(): "The size of the largest possible string." Now, I'm fine.

What's next?

I will have in the next post a closer look at std::array. std::array combines the best from two worlds. On one hand, std::array is as lightweight as a C array; on the other hand, std::array supports the same interface as a std::vector.

 

 

 

 

 

 

 

 

 

title page smalltitle page small Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library".   Get your e-book. Support my blog.

 

 

 

 

 

Tags: memory

Comments   

+1 #1 Pete 2016-12-17 18:13
I may be an idiot but I think that ascii chars are 8 bits which is a byte which is 1 in bytecount and the INT is four bytes. Which is 4.
Quote
+1 #2 Mark Abraham 2016-12-18 05:13
It would be good to report the versions of your compilers. The standard library in sufficiently recent VC implements the small string optimisation (SSO). That makes the minimum size 16 bytes, which the implementation handles differently for different sizes. Small strings have no dynamically allocated memory, which is a useful optimisation. Maybe they can also point to four dynamic allocations, which may explain the four-fold greater size with the VC standard library. However these conveniences make the string-related code more complicated and perhaps slightly slower.
Quote
0 #3 Lectem 2016-12-18 06:23
"the cl.exe compiler is a little bit more greedy. That holds in particular true for std::string" This has nothing to do with cl.exe, it is the MSVC STL implementation. The reason it gives a size of 15 is because it probably uses SSO (small string optimization)
Quote
0 #4 Rainer Grimm 2016-12-18 09:03
Quoting Pete:
I may be an idiot but I think that ascii chars are 8 bits which is a byte which is 1 in bytecount and the INT is four bytes. Which is 4.

Pete, you are right. My reasoning is totally broken. I will adjust it.
Quote
0 #5 neo2 2016-12-21 16:41
Howdy I am so thrilled I found your web site, I really found you by error, while
I was searching on Google for something else, Anyways I am here now and would just like to say
many thanks for a tremendous post and a all round enjoyable blog (I also love the theme/design), I don’t have time to browse
it all at the moment but I have bookmarked it and also added your RSS feeds,
so when I have time I will be back to read much more, Please do keep up
the great jo.
Quote

Add comment


My Newest E-Book

Latest comments

Subscribe to the newsletter (+ pdf bundle)

Blog archive

Source Code

Visitors

Today 136

All 279144

Currently are 137 guests and no members online