Adressen und Speichergrundlagen
Wie Speicheradressen funktionieren: Bytes, Layout und Zeigerreferenzen erläutert.
In C++ werden Daten im Speicher (Memory) des Computers gespeichert. Jede Variable besitzt eine eigene Adresse (Address) im Speicher. Das Verständnis von Speicheradressen ist die Grundlage für das Verständnis mächtiger C++-Konzepte wie Pointer (Zeiger). In diesem Artikel lernst du den Aufbau des Speichers, das Konzept von Adressen und wie Variablen im Speicher abgelegt werden.
1. Was ist Speicher?
Der Speicher (RAM) ist der Bereich, in dem Daten und Befehle während der Programmausführung vorübergehend gespeichert werden. Wenn das Programm beendet wird, werden die im Speicher gespeicherten Daten gelöscht. Der C++-Compiler reserviert Speicherplatz für Variablen und weist ihnen Adressen zu.
Im folgenden Beispiel hat jede Variable eine eigene Speicheradresse:
#include <iostream>
using namespace std;
int main() {
int alter = 25;
double pi = 3.14;
cout << "Adresse von alter: " << &alter << endl;
cout << "Adresse von pi: " << &pi << endl;
return 0;
}
Beispielausgabe:
Adresse von alter: 0x61ff0c
Adresse von pi: 0x61ff08
Jede Adresse stellt eine physische Position im Speicher dar und wird normalerweise im **hexadezimalen Format** angezeigt.
2. & (Adressoperator)
Der Operator & gibt die Adresse einer Variablen zurück —
also den Speicherort, an dem ihr Wert gespeichert ist.
int zahl = 100;
cout << "Adresse von zahl: " << &zahl << endl;
cout << "Wert von zahl: " << zahl << endl;
Die Adresse ist ein Ort, während der Wert die gespeicherte Information an diesem Ort ist. Diese Unterscheidung ist grundlegend, um Zeiger zu verstehen.
3. Speicheraufbau (Stack und Heap)
Wenn ein C++-Programm ausgeführt wird, wird der Speicher im Allgemeinen in zwei Hauptbereiche unterteilt:
| Bereich | Beschreibung | Verwendung |
|---|---|---|
| Stack | Enthält automatische (lokale) Variablen. Speicher wird beim Funktionsaufruf reserviert und nach dem Ende freigegeben. | Automatische Variablen (int, double usw.) |
| Heap | Bereich für dynamisch reservierten Speicher. Wird mit new und delete manuell verwaltet. |
Dynamische Datenstrukturen (Objekte, die mit new erstellt wurden) |
Beispiel:
int x = 10; // Im Stack gespeichert
int *p = new int(5); // Im Heap gespeichert
Der Zeiger p selbst wird im Stack gespeichert, aber der Wert, auf den er zeigt, liegt im Heap.
Dieser Unterschied ist wichtig, um Speicherlecks (Memory Leaks) zu vermeiden.
4. sizeof-Operator
Der Operator sizeof gibt an, wie viele Bytes eine Variable oder ein Datentyp im Speicher belegt.
#include <iostream>
using namespace std;
int main() {
cout << "Größe von int: " << sizeof(int) << " Byte" << endl;
cout << "Größe von double: " << sizeof(double) << " Byte" << endl;
cout << "Größe von char: " << sizeof(char) << " Byte" << endl;
return 0;
}
Typische Werte auf den meisten Systemen:
int→ 4 Bytesdouble→ 8 Byteschar→ 1 Byte
5. Visualisierung von Variablen im Speicher
Betrachten wir folgendes Beispiel:
int a = 5;
int b = 10;
Speicheransicht:
Adresse Wert
0x61ff0c → 5 (a)
0x61ff08 → 10 (b)
Jede Variable hat eine eigene Adresse. Diese Adressen können je nach Speicherlayout auf- oder absteigend sein.
6. Einführung in Pointer (Zeiger)
Ein Pointer ist eine spezielle Variable, die die Adresse einer anderen Variable speichert.
Der *-Operator (Stern) wird verwendet, um Pointer zu deklarieren.
#include <iostream>
using namespace std;
int main() {
int zahl = 42;
int *ptr = &zahl; // Adresse von zahl wird dem Pointer zugewiesen
cout << "Wert von zahl: " << zahl << endl;
cout << "Wert, auf den ptr zeigt: " << *ptr << endl;
cout << "Adresse, die ptr speichert: " << ptr << endl;
return 0;
}
&→ Adressoperator (gibt die Adresse einer Variablen zurück)*→ Dereferenzierungsoperator (greift auf den Wert an der Adresse zu)
In diesem Beispiel speichert ptr die Adresse der Variablen zahl.
Mit *ptr kann auf den gespeicherten Wert zugegriffen werden.
7. Null-Pointer und sichere Verwendung
Zeiger sollten niemals auf ungültige Speicheradressen zeigen.
Wenn ein Zeiger noch keiner Adresse zugewiesen wurde, sollte er mit nullptr initialisiert werden.
int *p = nullptr;
if (p == nullptr) {
cout << "Der Pointer zeigt derzeit auf keine Adresse." << endl;
}
Diese Überprüfung verhindert Laufzeitfehler und Segmentation Faults.
8. Pointer und Speicherdiagramm
Betrachten wir folgendes Beispiel:
int x = 10;
int *p = &x;
Speicheransicht:
Variable | Adresse | Wert
x | 0x61ff0c | 10
p | 0x61ff08 | 0x61ff0c
Der Pointer p speichert die Adresse von x.
Mit *p kann auf den Wert von x zugegriffen werden.
9. Speicherverwaltung und Sicherheit
In C++ bietet der direkte Zugriff auf den Speicher große Flexibilität, erfordert jedoch auch Verantwortung. Dynamisch reservierter Speicher muss mit delete wieder freigegeben werden.
int *p = new int(100); // Speicher im Heap reservieren
cout << *p << endl;
delete p; // Speicher freigeben
p = nullptr; // Vermeidet einen hängenden Zeiger (dangling pointer)
Ein Speicherleck (Memory Leak) tritt auf, wenn Speicher nicht ordnungsgemäß freigegeben wird.
Dies kann die Leistung des Programms beeinträchtigen.
Deshalb sollte jeder new-Aufruf mit einem delete abgeschlossen werden.
10. TL;DR
- Jede Variable besitzt eine Adresse im Speicher (
&Operator). - Stack → automatisch, Heap → dynamischer Speicherbereich.
sizeofzeigt, wie viel Speicher ein Typ belegt.int *p→ definiert einen Pointer,*p→ greift auf den Wert zu.nullptrsollte als sicherer Anfangswert verwendet werden.- Mit
newreservierter Speicher muss mitdeletefreigegeben werden. - Alle Beispiele können in Visual Studio 2022 oder GCC 11+ ausgeführt werden.