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

Bearbeiten

Diese 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.

Hinweis

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:

//x ist automatisch double
auto x = 3.14; // Korrekt nach C++11

Implementiert wurde dies im g++ seit Version 4.4[1]

register

Bearbeiten

Durch 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.

Die 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.

Variablen 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.

Diese 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

Bearbeiten

Variablen 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:

#include <vector>

void print(const std::vector<int>& v); // pass-by-const-reference

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

Bearbeiten

Mit 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.

Referenzen

Bearbeiten
  1. http://gcc.gnu.org/projects/cxx0x.html

Heapspeicher

Bearbeiten

Verwendung

Bearbeiten

Beispiele

Bearbeiten