Wird geladen...

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:

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


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;
}

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.
  • sizeof zeigt, wie viel Speicher ein Typ belegt.
  • int *p → definiert einen Pointer, *p → greift auf den Wert zu.
  • nullptr sollte als sicherer Anfangswert verwendet werden.
  • Mit new reservierter Speicher muss mit delete freigegeben werden.
  • Alle Beispiele können in Visual Studio 2022 oder GCC 11+ ausgeführt werden.

Ähnliche Artikel