C++-Programmierung/ Speicherverwaltung/ Speicherklassen
Wo werden Variablen angelegt? Im Datenbereich (Datensegment), auf dem Stapelspeicher (Stack), auf der Halde (Heap)? C++ erlaubt, in eingeschränktem Maße selbst zu wählen.
Speicherklassen
Bearbeitenauto
BearbeitenDiese Speicherklasse zu wählen, ist nicht nötig, da implizit immer auto
verwendet wird, wenn innerhalb von Quelltexten eine Variable deklariert wird. Diese Variable bekommt dann automatisch Speicher auf dem Stack zugeteilt. Sobald ein Gültigkeitsbereich verlassen wird, in dem eine Variable mit Speicherklasse auto
erzeugt wurde, wird dieser Speicher wieder freigegeben.
Bemerkung zum neuen C++2011-Standard:
Seit dem neuen Standard hat das Schlüsselwort auto
eine neue Bedeutung und zwar die der automatischen Typbestimmung von Variablen. Das bedeutet, wenn man vor einer Variablen auto
setzt, so wird nicht mehr verlangt, ein Typ wie int
explizit davor zu schreiben.
Beispiel:
Implementiert wurde dies im g++ seit Version 4.4[1]
register
BearbeitenDurch die Verwendung der Speicherklasse register
wird dem Compiler der Hinweis gegeben, den Speicherbereich für eine Variable in möglichst schnellen Speicher zu legen. In der Vergangenheit, insbesondere bei klassischem C wurde register
dafür verwendet, um die Speicherung einer Variablen eines Integraltyps in einem Prozessorregister zu erzwingen. Aktuelle Compiler versuchen heutzutage Variablen jeden Typs, die mit register
deklariert wurden, in 'gecachten' Prozessorspeicher zu legen oder teilen bei der Speicherallokation dem Betriebssystem mit, dass die entsprechenden Speicherseiten nicht in virtuellem Speicher ausgelagert werden dürfen.
Sie sollten register
nur verwenden, wenn Sie Ihren Compiler und die Zielplattform sehr gut kennen.
static
BearbeitenDie Speicherklasse static
weist den Compiler an, dass der Speicherbereich der Variable während der gesamten Laufzeit des Programms zur Verfügung stehen soll und nicht von einem Gültigkeitsbereich bzw. dessen Verlassen abhängt. Dies hat verschiedene Auswirkungen:
- Eine
static
-Variable innerhalb einer Funktion bleibt auch nach Ausführung der Funktion bestehen. Der Wert und die Adresse bleiben zwischen zwei Funktionsaufrufen erhalten. - Die Deklaration ist in der gesamten Übersetzungseinheit gültig/zugreifbar. Dadurch wird ein Binärobjekt innerhalb eines Programmmoduls zur Speicherbereichsgrenze im gesamten Programm da der C++-Standard verlangt, dass
static
-Deklarationen bei der Verlinkung nur im aktuellen Modul sichtbar sind. - Bei Multithreading muss eine Zugriffssynchronisierung erfolgen.
- Mitgliedsattribute von Klassen, die mit der Speicherklasse
static
deklariert wurden, sollten eine zusätzliche Deklaration und Definition erhalten, die vom Compiler nur einmal angewendet wird.
extern
BearbeitenVariablen mit der Speicherklasse extern
beschreiben Speicher, der in anderen Übersetzungseinheiten zugewiesen wird und in der aktuellen Übersetzungseinheit importiert wird. Es muss ein direkter Zugriff auf den Speicher im Quellbereich vorhanden sein, in der Regel dadurch, dass die Variable dort global oder als öffentliches, statisches Klassenattribut deklariert wurde.
mutable
BearbeitenDiese Speicherklasse ist nur sinnvoll, wenn Sie sich absolut sicher sind, dass ein Attribut einer Klasse keine Auswirkung auf deren Zustand bzw. Ihre Programmierlogik hat, oder wenn Sie erlauben wollen, dass konstante Zugriffsmethoden Objekte vor Verwendung initialisieren. Sie wird dafür verwendet, um aus konstant deklarierten Methoden auf ein nichtkonstantes Attribut zuzugreifen und dieses zu ändern. mutable
kann beispielsweise einem konstant deklarierten Zugriffsoperator ermöglichen, ein Objekt einmalig auf dem Heap anzulegen, für das im privaten Bereich einer Klasse eine auf null
gesetzte Zeigervariable liegt:
#include <string>
struct K : std::string {
std::string::size_type const len() const {return this->size();}
};
class U {
mutable K * m_pK;
public:
U() : m_pK(0) {};
K const * getK() const {return m_pK==0 ? m_pK=new K() : m_pK;}
};
int main(int argc, char* argv[]) {
U U1;
U1.getK()->len();
return 0;
}
Das Beispiel zeigt, wie hilfs mutable
die konstante Methode K const * getK() const
die unter m_pK
gespeicherte Adresse ändern konnte.
Qualifizierer
Bearbeitenconst
BearbeitenVariablen mit diesem Qualifizierer sind nach der Definition unveränderbare Konstanten, die - anders als beispielsweise in C - kompilierzeitkonstant sind. Das bedeutet, dass bei der Deklaration einer Konstanten ihr auch ein Wert zugewiesen werden muss. Die Gründe für diese Entscheidung sind klar: Wie könnte eine const
-Variable eine Konstante sein, wenn ihr kein Wert zugewiesen wurde? Generell ist es eine gute Idee, auch "normale" Variablen bei ihrer Deklaration zu initialisieren, falls es einen sinnvollen Anfangswert gibt.
const int x = 7; // Initialisierer - verwendet die =-Syntax
const int x2(9); // Initialisierer - verwendet die ()-Syntax
const int y; // Fehler: kein Initialisierer
Das Schlüsselwort const
kann aber auch in Verbindung mit (Member-)Funktionen genutzt werden. So wird in dieser Deklaration festgelegt, dass die Methode das Argument nicht verändern darf und kann:
Im Zusammenspiel mit Memberfunktionen macht const
deutlich, dass die Methode das Objekt, für das es aufgerufen wurde, nicht verändert. Die Funktion kann also auch für const
-Objekte aufgerufen werden.
class Car {
public:
explicit Car() : _km(0) {}
int getKm() const { return _km; }
private:
int _km;
};
volatile
BearbeitenMit dem Schlüsselwort volatile
vor einem Variablentyp (bei der Deklaration einer Variablen) wird der Compiler angewiesen, die Variable bei jedem Zugriff erneut aus dem Speicher zu laden bzw. bei schreibendem Zugriff die Variable sofort in den Speicher zu schreiben. Ohne volatile
würde die Optimierung des Compilers möglicherweise dazu führen, dass die Variable in einem Prozessorregister zwischengespeichert wird.
volatile
ist somit gewissermaßen das Gegenteil zu register
.
volatile
wird dann verwendet, wenn zu erwarten ist, dass auf den Wert der Variablen von außerhalb des Programms zugegriffen wird. Solch ein Zugriff könnte beispielsweise durch einen anderen Prozess/ Thread, durch das Betriebssystem oder durch die Hardware stattfinden. Ein typischer Anwendungsfall ist ein Interrupt (also eine Unterbrechung des aktuellen Programms zur Behandlung auftretender Ereignisse). Eine Zwischenspeicherung der Variablen würde dazu führen, dass das Programm nicht mit dem geänderten Wert arbeitet und diesen möglicherweise sogar überschreibt.