Es klingt wie ein Rekursionsalbtraum, aber Zeiger auf Zeiger sind ein Grundbestandteil der Systemprogrammierung. Sie zeigen im Wesentlichen auf eine Speicheradresse, die die Adresse Ihrer tatsächlichen Daten enthält. Diese doppelschichtige Indirektion wird von Entwicklern als Handle bezeichnet. Es ist nicht nur ein Codierungstrick. Es handelt sich um einen notwendigen Mechanismus für Betriebssysteme zur effizienten Verwaltung des Heap-Speichers.
Betrachten Sie den Haufen als einen überfüllten Raum. Manchmal muss das Betriebssystem die Leute verschieben, um Platz zu schaffen. Wenn Sie einen direkten Zeiger auf jemanden halten, kann dieser sich nicht bewegen, ohne Ihren Verweis zu unterbrechen. Wenn Sie jedoch einen Zeiger auf eine Namensliste halten, kann das Betriebssystem den Standort jeder Person in dieser Liste ändern, ohne dass Sie Ihre ursprüngliche Referenz aktualisieren müssen. Das ist die Kraft eines Griffs.
So wird diese Logik in rohen C-Code übersetzt. Sie deklarieren „p“ als Zeiger auf einen Zeiger. Dann wird „q“ zu einem Standardzeiger. Sie weisen Speicher für „p“ zu und weisen dann Speicher für das zu, worauf „p“ zeigt. Abschließend dereferenzieren Sie zweimal, um den Wert 12 zuzuweisen.
„c
int p;
int *q;
p = (int )malloc(sizeof(int ));
p = (int )malloc(sizeof(int));
p = 12;
q = p;
printf(“%d\n”, *q);
frei(q);
frei(p);
„
Windows und macOS verlassen sich bei der Speicherkomprimierung auf diese Struktur. Die Unterscheidung ist hier von entscheidender Bedeutung. Sie als Programmierer verwalten den äußeren Zeiger „p“. Das Betriebssystem verwaltet den inneren Zeiger „p“. Da das Betriebssystem „ p“ steuert, kann es den tatsächlichen Datenblock (*p) an eine beliebige Stelle im Heap verschieben. Es aktualisiert einfach „ p“, um die neue Adresse widerzuspiegeln. Ihr Code verwendet weiterhin „p“ ohne Probleme.
Über die Speicherverwaltung des Betriebssystems hinaus ist dieses Muster für die Übergabe von Zeigern an Funktionen unerlässlich. Wenn Sie eine Funktion benötigen, um einen Zeiger selbst zu ändern, übergeben Sie einen Zeiger an diesen Zeiger. Dies ist die einzige Möglichkeit, die Referenz innerhalb eines anderen Bereichs neu zuzuweisen.
Zeiger auf Strukturen verwalten
Die Komplexität hört nicht bei einfachen ganzen Zahlen auf. Sie können diese Logik in Strukturen verschachteln. Dies ist häufig bei der Verarbeitung von Daten variabler Länge wie Zeichenfolgen der Fall.
Nehmen Sie die Struktur „Addr“. Es enthält Arrays fester Größe für Namen, Städte und Telefone. Aber Kommentare variieren in der Länge. „Kommentar“ ist also als Zeiger auf char definiert. Wenn Sie Speicher für die Struktur selbst reservieren, reservieren Sie Platz für die Zeiger und die festen Arrays. Sie reservieren noch keinen Platz für den eigentlichen Kommentartext.
„c
typedef struct {
char name[21];
Char-Stadt[21];
char phone[21];
char *Kommentar;
} Addr;
Addr *s;
char comm[100];
s = (Addr *)malloc(sizeof(Addr));
gets(s->name, 20);
gets(s->city, 20);
gets(s->phone, 20);
gets(comm, 100);
s->comment = (char *)malloc(sizeof(char[strlen(comm)+1]));
strcpy(s->comment, comm);
„
Sie weisen zuerst „s“ zu. Anschließend lesen Sie Benutzereingaben in die festen Puffer ein. Anschließend liest man den Kommentar in ein Provisorium ein

Sie verlieren Speicher, wenn Sie ignorieren, wie Zeiger verschachtelt werden.
Nehmen Sie einen Zeiger „s“, der auf eine Struktur zeigt. Diese Struktur enthält einen weiteren Hinweis. Dieser zweite Zeiger zeigt auf eine tatsächliche Zeichenfolge im Speicher. Zwei Indirektionsebenen. Eine Zuweisung für die Struktur. Eine Zuordnung für die Zeichenfolge.
Einfach genug, bis Sie versuchen, aufzuräumen.
Hier stolpern die meisten Entwickler. Sie sehen „frei(s)“. Du denkst, du bist fertig. Sie liegen falsch.
Schauen Sie sich dieses Code-Snippet an:
„c
s = (Addr )malloc(sizeof(Addr));
gets(comm, 100);
s->comment = (char )malloc(sizeof(char[strlen(comm)+1]));
strcpy(s->comment, comm);
kostenlos(s);
„
Die Variable „s“ zeigt auf die Struktur „Addr“. Innerhalb dieser Struktur gibt es ein Feld „Kommentar“. „comment“ zeigt auf einen Block Heap-Speicher, der für die String-Daten reserviert ist.
Wenn Sie „free(s)“ aufrufen, geben Sie den Speicher für die Struktur selbst frei. Die Struktur „Addr“ ist verschwunden. Der Speicher wird an das System zurückgegeben.
Aber was passiert mit „s->comment“?
Es verschwindet im Äther.
Der Zeiger auf die Zeichenfolgendaten wurde in der Struktur gespeichert, die Sie gerade freigegeben haben. Sie können nicht mehr darauf zugreifen. Sie haben „free()“ für die Zeichenfolge nicht aufgerufen. Der Speicher bleibt zugewiesen, aber nicht erreichbar.
Dies ist ein Speicherverlust.
Es ist kein Absturz. Es handelt sich nicht um eine Fehlermeldung. Das Programm läuft einwandfrei. Es verbraucht nur langsam mehr RAM, bis das System wechselt oder abstürzt.
So beheben Sie Doppelzeigerlecks
Sie müssen zuerst den inneren Zeiger freigeben. Oder speichern Sie es in einer temporären Variablen, bevor Sie die äußere Struktur freigeben.
„c
kostenlos(s->Kommentar);
kostenlos(s);
„
Jetzt werden die String-Daten freigegeben. Anschließend wird die Struktur freigegeben. Keine verlorenen Blöcke.
Warum das so oft passiert
Menschen behandeln Hinweise als Werte. Sie vergessen, dass Zeiger Verweise auf Ressourcen sind.
Wenn eine Struktur einen Zeiger enthält, ist diese Struktur nicht in sich geschlossen. Es ist auf externen Speicher angewiesen. Durch die Freigabe des Containers wird der Inhalt nicht freigegeben.
Bei tieferer Verschachtelung wird dies schlimmer. Ein Zeiger auf einen Zeiger auf einen Zeiger. Drei Ebenen. Sie benötigen drei „free()“-Aufrufe. In der richtigen Reihenfolge.
Wenn man eins vergisst, läuft man aus.
Das gets()-Problem
Das Beispiel verwendet „gets()“. Diese Funktion ist gefährlich. Es wurde aus dem C11-Standard entfernt, da es Pufferüberläufe zulässt. Die Speicherlogik bleibt jedoch dieselbe.
Unabhängig davon, ob Sie „gets()“, „fgets()“ oder „scanf()“ verwenden, ist die Zuordnungsstrategie hier der Flaschenhals für Lecks.
Sie weisen für „s“ zu.
Sie weisen für „s->comment“ zu.
Wenn Sie nur „s“ freigeben, hinterlassen Sie „s->commentar“.
Wichtige Erkenntnisse
- Doppelte Zeiger erfordern doppeltes Freigeben.
- Überprüfen Sie jedes „malloc“ auf ein entsprechendes „free“.
– Wenn eine Struktur einen Zeiger auf dynamischen Speicher enthält, muss dieser Speicher vor der Struktur freigegeben werden. - „free()“ gibt Mitglieder nicht rekursiv frei.
Es ist leicht zu übersehen. Der Code wird kompiliert. Es läuft. Das Leck ist still.
Bis es nicht mehr der Fall ist.

Erstellen verknüpfter Listen
Es kann zu einer Speicherkatastrophe kommen, wenn Sie den Container vor den Daten entsorgen, auf die er verweist. Die Struktur, die den Zeiger hält, wird bereinigt. Der String-Block bleibt bestehen. Es wird zu einem verlorenen Block. Dies geschieht, wenn die Reihenfolge der Entsorgung falsch ist.
Verlinkung
Strukturen können auf sich selbst verweisen. Dadurch können Sie identische Datensätze miteinander verketten. Das Ergebnis ist eine verknüpfte Liste. Es handelt sich um eine Standardmethode zum Organisieren von Daten in C.
So definieren Sie es:
typedef struct { char name[21]; Char-Stadt[21]; char state[21]; Addr next; } Addr;
Addr first;
Das Feld „next“ enthält die Adresse des nachfolgenden Datensatzes. Sie verwenden eine einzelne Zeigervariable, um die Kette zu starten.

Die Kosten der Flexibilität
Mit dem Compiler können Sie Regeln ändern, die auf den ersten Blick möglicherweise nicht intuitiv erscheinen. Mit genügend Erfahrung können Sie Strukturen konstruieren, die wie die oben gezeigte aussehen. Es ist eine Machtbewegung.
Aber es ist nicht ohne Risiko. Sie bewegen sich auf einem schmalen Grat zwischen cleverem Code und nicht wartbarem Spaghetti.
Warum es wichtig ist
Dabei geht es nicht nur um Syntax. Es geht darum, was die Sprache zulässt, wenn man gegen ihre Beschränkungen anstößt.
- Kontrolle : Sie erhalten eine detaillierte Kontrolle über das Speicherlayout.
- Interoperabilität : Sie können einfacher mit C-Bibliotheken kommunizieren.
- Leistung : Manchmal spart das Umgehen von Sicherheitsprüfungen Zyklen.
Der Haken? Der Compiler wird Sie nicht vor sich selbst retten. Damit können Sie Code kompilieren, der zur Laufzeit abstürzt.
So verwenden Sie es sicher
Wenn Sie dies tun, tun Sie es mit Absicht.
- Isolieren : Verteilen Sie dieses Muster nicht in Ihrer Codebasis. Bewahren Sie es in einem kleinen, gut getesteten Modul auf.
- Dokumentieren Sie alles : Zukünftig werden Sie sich bei Ihnen bedanken. Oder dich hassen. Wahrscheinlich hasse ich dich.
- Abstraktionen verwenden : Wickeln Sie die Rohzeiger oder unsicheren Blöcke in eine saubere Schnittstelle ein. Verstecke das Chaos.
Der Realitätscheck
Die meisten Entwickler brauchen das nicht. Sie verwenden Standardstrukturen. Sie werden darüber glücklicher sein.
Aber wenn Sie an eine Wand stoßen, an der die Standardbibliothek nicht passt, werden Sie froh sein, dass der Compiler Sie nicht aufgehalten hat.
Die Frage ist, ob Sie bereit sind, die Wartungskosten zu zahlen.
Es ist ein Kompromiss. Geschwindigkeit für Sicherheit. Kraft für Klarheit.
Sie wählen.



















