Automatisches Speichermanagement mit Containern

Inhaltsverzeichnis[Anzeigen]

Einer der großen Vorteile des C++-Strings gegenüber dem C-String, bzw. dem std:::vector gegenüber dem C-Array ist es, dass diese C++ Datenstrukturen automatisch ihren Speicher verwalten. Dies trifft natürlich neben dem std::string auf alle Container der Standard Template Library zu. Diese automatische Speicherverwaltung will ich mir in diesem Artikel für den std::vector und den std::string genauer anschauen.

 

Von der Perspektive eines Anwenders betrachtet, verhält sich ein std::string insbesondere mit C++11 sehr ähnlich wie ein std::vector. Das ist der einfache Grund, dass ich beide Datenstrukturen gleichzeitig vorstellen kann. Da trifft es sich gut, dass std::string und std::vector die wichtigsten Container in C++ sind.

std::string und std::vector

Die C++-Laufzeit passt die Größe des std::string und des std::vector automatisch an die Anzahl seiner Elemente an. Und das mit C++11 nicht nur in eine Richtung.

 

 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;

}

 

Das Programm ist fast selbsterklärend. Dachte ich zu mindestens. Dazu aber gleich mehr.

Um Tipparbeit zu sparen, habe ich mir die kleine Funktion showInfo (Zeile 7 - 13) definiert, die mir für einen konkreten Container dessen Größe (Zeile 10) und Kapazität (Zeile 11) ausgibt. Die Größe eines Containers ist die Anzahl seiner Elemente, die Kapazität eines Containers ist die Anzahl der Elemente, die er maximal besitzen kann, ohne dass eine Reallokierung stattfinden muss. Daher gibt natürlich, dass die Kapazität eines Containers immer mindestens so groß ist wie seine Größe. Während die Größe eines Containers durch die Methode resize (Zeile 49 und 50) vergrößert wird, lässt sich seine Kapazität mit der Methode reserve (Zeile 56 und 57) anpassen.

Nun aber das Programm von Anfang bis zum Ende. In Zeile 19 und 20 erzeuge ich einen leeren String und einen Vector. Dann stelle ich die maximale Anzahl der Elemente dar, die ein String bzw. Vektor besitzen kann. Nach jeder weiteren Operationen auf dem std::string und dem std::vector gebe ich dessen Größe und Kapazität aus. Das trifft für die Initialisierung der Container (Zeile 34 und 35), für das Hinzufügen von vier Elementen (Zeile 42 und 43) zum Container, das Vergrößern der Container auf 30 Elemente (Zeile 49 und 50) und das Reservieren von zusätzlichem Speicherplatz für 1000 Elemente (Zeile 56 und 57) zu. Selbst das Verkleinern des Containers auf seine tatsächliche Größe wird mit der Methode shrink_to_fit (Zeile 63 und 64) in C++11 unterstützt. 

Bevor ich die Ausgabe des Programms auf Linux und Windows vorstelle, möchte ich meine wichtigsten Beobachtungen zusammenfassen.

  1. Die Anpassung der Größe und Kapazität der Container geschieht automatisch. Ich verwende in diesem Programm keine expliziten Aufrufe zur Speicherallokation oder -deallokation.
  2. std::string und std::vector unterstützen das gleiche Interface bei meinen Operationen. Lediglich in Zeile 41 habe ich einen C-String zum bestehenden C++-String hinzu addiert.
  3. Durch die Methodenausführung cont.resize(n) erhält der Container cont neue default-initialisierte Elemente, wenn n > cont.size() ist.
  4. Durch die Methodenausführung cont.reserve(n) wird neuer Speicher für mindestens n Elemente für cont reserviert, wenn  n > const.capacity() ist.
  5. Der Aufruf der Methode shrink_to_fit ist nicht bindend. Das heißt, die C++-Laufzeit muss die Kapazität des Containers nicht an seine Größe anpassen. Bei all meine Anwendungen von shrink_to_fit mit GCC, clang oder cl.exe wurde der unnötige Speicher ab immer freigegeben.

Nun aber zur Ausgabe des Programms.

 

stringAndVectorstringAndVectorWin

Meine kleine Verwunderung

Schön zeigt das Programm, dass der cl.exe Compiler unter Windows ein bisschen gieriger ist. Dies trifft insbesondere auf den std::string zu. So besitzt der leere std::string Speicher für 15 Elemente. Mehr hat mich aber gewundert, dass unter Windows die maximale Größe eines std::string um den Faktor 4 größer ist als die eines std::vector<int>. Dieser Faktor 4 gilt auch zwischen einem std::string unter Windows und Linux. Dies gilt, obwohl die Datentypen char und int gleich groß sind.

Aber ich war mehr über die Tatsache verwundert, dass die maximale Größe eines std::string unter Linux die gleiche ist die maximale Größe eines std::vector<int>. Das ist verwunderlich, ist doch int viermal so groß wie char unter Linux und 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

 

Diese Werte lassen sich nur als Maximalwerte interpretieren. 

Wie geht's weiter?

Im nächsten Artikel stelle ich std::array genauer vor. std::array vereint das beste aus zwei Welten. Zum einen ist er so leichtgewichtig wie das C-Array, zum anderen bietet er das Interface eines std::vector an.

 

 

 

 

 

 

 

 

 

Kommentare   

+1 #1 Marcel Wid 2016-05-30 15:53
Hier werden Äpfel mit Birnen verglichen :
1. mit gcc wird eine 64bit Anwendung erstellt, mit cl eine 32bit Anwendung

2. Die string Implementierung von gcc ist nicht C++11 konform, erst mit gcc 5 gibt es eine konforme Implementierung, die dann auch SSO verwendet.
Zitieren
+1 #2 Rainer Grimm 2016-05-30 17:28
zitiere Marcel Wid:
Hier werden Äpfel mit Birnen verglichen :
1. mit gcc wird eine 64bit Anwendung erstellt, mit cl eine 32bit Anwendung

2. Die string Implementierung von gcc ist nicht C++11 konform, erst mit gcc 5 gibt es eine konforme Implementierung, die dann auch SSO verwendet.

OK, der Hauptpunkt des Artikels war nicht der Vergleich, sondern, dass das Interface der vectors und des strings sehr ähnich sind. Der Vollständigkeit halber habe ich die Ausgabe unter Linux und Windows noch dargestelllt und war verwundert. Genau diesen Punkt bringe ich ich durch die Überschrift explizit zum Ausdruck. Da werde ich nochmals über den Text gehen und die Windows Anwendung auf 64bit anpassen.
Mich verwundert, dass der GCC noch Referenz Counting für seine String Implemtierung verwendet, obwohl dies durch den Standard (C.2.11) nicht mehr zulässig ist. Laut Standard kann er nun small-string optimization (SSO) einsetzen. Die Frage ist natürlich, wie lang ist ein "small string".
Zitieren
0 #3 เสื้อคลุมลูกไม้ 2016-12-01 13:41
I drop a comment each time I especially enjoy a post on a site or I have something to add to the conversation. Usually it's caused by the
fire communicated in the article I browsed. And on this article Automatisches Speichermanagement mit Containern. I
was actually excited enough to drop a commenta response ;-)
I do have 2 questions for you if you usually do not mind. Could it be only me or do a few of these
comments come across as if they are coming from brain dead people?
:-P And, if you are writing at other online
social sites, I'd like to follow everything new you have to post.
Could you list the complete urls of all your
communal sites like your linkedin profile, Facebook page
or twitter feed?
Zitieren

Kommentar schreiben


Modernes C++

Abonniere den Newsletter

Inklusive zwei Artikel meines Buchs
Introduction und Multithreading

Beiträge-Archiv

Sourcecode

Neuste Kommentare