Adressen und Speichergrundlagen in C++
Lernen Sie die Grundlagen von Adressen und Speicher in C++, einschließlich Speicheraufbau und Variablenadressen.
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. Speicherstruktur (Stack und Heap)
Wenn ein C++-Programm ausgeführt wird, wird der Speicher im Allgemeinen in zwei Hauptbereiche unterteilt:
| Bereich | Beschreibung | Verwendung |
|---|---|---|
| Stack | Speichert automatische (lokale) Variablen. Speicher wird beim Aufruf einer Funktion reserviert und beim Beenden der Funktion automatisch wieder freigegeben. | Variablen mit automatischer Lebensdauer (int, double usw.) |
| Heap | Wird für dynamisch reservierten Speicher verwendet. Die Verwaltung erfolgt manuell mit new und delete. |
Dynamische Datenstrukturen (mit new erzeugte Objekte) |
Leistungshinweis: Der Stack ist äußerst schnell, da die Speicherreservierung lediglich das Verschieben eines internen Zeigers erfordert. Der Heap ist dagegen langsamer, da das Betriebssystem einen freien Speicherbereich in passender Größe suchen und reservieren muss.
Beispiel:
int x = 10; // Liegt auf dem Stack (schnell und automatisch)
int *p = new int(5); // Liegt auf dem Heap (langsamer, manuelle Freigabe erforderlich)
Beachten Sie, dass sich der Zeiger p selbst auf dem Stack befindet,
der Wert, auf den er zeigt, jedoch auf dem Heap gespeichert ist.
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 die direkte Speicherverwaltung große Flexibilität, bringt aber auch Verantwortung mit sich. Jeder dynamisch reservierte Speicher muss mit delete wieder freigegeben werden.
int *p = new int(100); // Speicher auf dem Heap reservieren
cout << *p << endl;
delete p; // Speicher freigeben
p = nullptr; // Dangling Pointer vermeiden
Ein Speicherleck (Memory Leak) entsteht, wenn reservierter Speicher nicht ordnungsgemäß freigegeben wird.
Dadurch kann ein Programm im Laufe der Zeit immer mehr Speicher verbrauchen und langsamer werden.
Deshalb sollte jedes new mit einem delete kombiniert werden.
Das Verständnis von Raw Pointern und manueller Speicherverwaltung ist wichtig, um C++ zu lernen.
Modernes C++ (C++11 und neuer) empfiehlt jedoch, sie im Produktivcode möglichst nicht direkt zu verwenden.
Stattdessen stellt die Standardbibliothek über den Header <memory>
Smart Pointer (wie std::unique_ptr und
std::shared_ptr) bereit. Diese übernehmen die Speicherfreigabe automatisch
und vermeiden dadurch weitgehend Speicherlecks sowie Probleme mit Dangling Pointern.
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.