Menu

Zeiger auf Strukturen in C: der Pfeiloperator und Strukturen auf dem Heap

Wie man in C auf eine Struktur zeigt - der Pfeiloperator, warum (*p).x seine Klammern braucht, Strukturen per Zeiger übergeben, um sie zu ändern oder Kopien zu vermeiden, eine Struktur mit malloc belegen und einen Knoten einer verketteten Liste bauen.

Diese Seite enthält ausführbare Editoren - bearbeiten, ausführen und Ausgabe sofort sehen.

Eine an eine Funktion übergebene Struktur wird kopiert. Das ist für einen Punkt aus zwei ints in Ordnung und für einen 200-Byte-Datensatz verschwenderisch - und es macht Änderungen unmöglich, denn die Funktion sieht immer nur ihre eigene Kopie. Auf eine Struktur zu zeigen löst beide Probleme auf einmal, und es ist auch der einzige Weg, Strukturen zu bauen, die wachsen: Listen, Bäume, Graphen.

Einen Zeiger auf eine Struktur nehmen

Es braucht nichts Besonderes. & gibt dir die Adresse, und der Typ ist „Zeiger auf Struktur":

Alle drei geben 3 aus. Die letzten beiden sind dieselbe Operation in zwei Schreibweisen.

Warum (*ptr).x seine Klammern braucht

Du könntest versuchen, sie wegzulassen und *ptr.x zu schreiben. Das übersetzt sich zu etwas ganz anderem oder verweigert öfter noch die Übersetzung ganz - denn der Elementoperator . hat höheren Vorrang als die Dereferenzierung *:

*ptr.x        // liest sich als *(ptr.x)  -- falsch
(*ptr).x      // zuerst dereferenzieren, dann das Element nehmen -- korrekt
ptr->x        // dasselbe, kurz gesagt

ptr.x fragt nach einem Element des Zeigers, und ein Zeiger hat keine Elemente. Die Beschwerde des Compilers („request for member 'x' in something not a structure or union") ist ein getarnter Vorrangfehler.

Weil diese geklammerte Form hässlich und leicht falsch zu machen ist, bietet C -> an. In der Praxis wirst du (*p).x in echtem Code fast nie sehen; greif zu p->x und vergiss, dass es die lange Form gibt - außer als Erklärung dafür, was der Pfeil bedeutet.

Eine Struktur per Zeiger übergeben

Ein Zeigerparameter gibt der Funktion die tatsächliche Struktur des Aufrufers, Änderungen bleiben also bestehen:

Zwei Gewohnheiten lohnen sich hier:

  • Markiere nur lesende Zeiger als const. const struct Point *p verspricht, dass die Funktion nicht ändert, worauf p zeigt. Fügt jemand später ein p->x = 0 darin ein, scheitert die Übersetzung, statt dass der Bug ausgeliefert wird. Es dokumentiert die Absicht auch an der Aufrufstelle.
  • Übergib große Strukturen auch beim Lesen per Zeiger. Eine Struktur mit einem 64-Byte-Namenspuffer kostet pro Wertübergabe 64 Bytes Kopie; ein Zeiger kostet 8.

Kleine Strukturen darf man weiterhin als Wert übergeben - struct Point mit 8 Bytes kopiert sich so billig wie ein Zeiger, und die Wertfassung kann nicht NULL sein, was eine ganze Fehlerart beseitigt.

Zeiger in Arrays von Strukturen

Zeigerarithmetik funktioniert auf Strukturarrays genauso wie auf int-Arrays: p + 1 rückt um eine ganze Struktur vor, Füllbytes inklusive.

Ein Arrayname zerfällt zu einem Zeiger auf sein erstes Element, staff ist beim Aufruf also bereits ein struct Employee *. Deshalb braucht die Funktion auch ein separat übergebenes n - sizeof würde innerhalb der Funktion den Zeiger messen, nicht das Array.

Eine Struktur auf dem Heap belegen

Strukturen auf dem Stack sterben am Ende ihres Gültigkeitsbereichs. Um eine zu erzeugen, die die Funktion überlebt, die sie erstellt hat - oder so viele, wie die Eingabe verlangt -, belege mit malloc:

Vier Einzelheiten in dieser kleinen Funktion sind alle tragend:

  • sizeof *e, nicht sizeof(struct Employee). Es liest sich als „die Größe dessen, worauf e zeigt", ändert sich der Typ also je, folgt die Belegung automatisch. Die beiden können nicht auseinanderdriften.
  • Prüfe auf NULL. malloc liefert NULL, wenn es die Anfrage nicht erfüllen kann. e->id über einen Nullzeiger zu schreiben ist ein Segmentierungsfehler.
  • Initialisiere jedes Element. malloc nullt den Speicher nicht; die Struktur kommt voller Müll an. (calloc(1, sizeof *e) nullt sie für dich.)
  • Irgendjemand muss sie mit free freigeben. Einen belegten Zeiger zurückzugeben überträgt diese Pflicht an den Aufrufer. Schreib es in einen Kommentar - eine unklare Besitzregel ist der Anfang von Speicherlecks.

Der Gewinn: ein Knoten einer verketteten Liste

Hier ist das, was Strukturen ohne Zeiger nicht können. Eine Struktur kann sich nicht selbst enthalten - das wäre unendlich groß -, aber sie kann einen Zeiger auf ihresgleichen enthalten, und dieser eine Trick baut jede verkettete Datenstruktur in C.

Beachte, dass struct Node *next; sich auf struct Node bezieht, während struct Node noch deklariert wird. Das ist genau deshalb erlaubt, weil ein Zeiger eine bekannte Größe hat, egal worauf er zeigt - der Compiler braucht die vollständige Definition noch nicht. Es ist auch die eine Stelle, an der du das Tag struct Node benutzen musst, selbst wenn du zusätzlich ein typedef geschrieben hast: Der typedef-Name existiert innerhalb seiner eigenen Definition noch nicht.

Die Schleife in free_list sichert head->next, bevor sie free(head) aufruft. head->next nach dem Freigeben zu lesen ist ein Use-after-free - der Speicher gehört dir nicht mehr, und der gelesene Wert ist das, was der Allokator dort abgelegt hat.

Häufige Fehler

  • p->x auf einem uninitialisierten oder NULL-Zeiger. Die häufigste Absturzursache in strukturlastigem Code. Initialisiere Zeiger mit NULL und prüfe vor dem Dereferenzieren.
  • Einen Zeiger auf eine lokale Struktur zurückgeben. struct Point *bad(void) { struct Point p = {1,2}; return &p; } liefert die Adresse von Speicher, der in dem Moment aufhört zu existieren, in dem die Funktion zurückkehrt. Gib die Struktur als Wert zurück oder belege sie.
  • Doppelt freigeben oder das Freigeben vergessen. Jedes malloc paart sich mit genau einem free. Setze den Zeiger nach dem Freigeben auf NULL, damit eine spätere versehentliche Nutzung laut abstürzt, statt still Speicher zu beschädigen.
  • Eine Struktur kopieren, die einen Zeiger enthält. Die Strukturzuweisung kopiert den Wert des Zeigers, nicht das, worauf er zeigt. Beide Strukturen verweisen nun auf denselben Puffer, und welche ihn zuerst freigibt, lässt die andere baumeln.

Häufig gestellte Fragen

Was macht der Operator -> in C?

p->x greift über den Zeiger p auf das Element x zu. Es ist exakt gleichwertig mit (*p).x - den Zeiger dereferenzieren und dann das Element nehmen. Den Pfeil gibt es, weil Zeiger auf Strukturen so verbreitet sind, dass die längere Form den Code überfrachtet.

Warum funktioniert *p.x bei einem Strukturzeiger nicht?

Weil . stärker bindet als *. *p.x liest sich als *(p.x) - nimm das Element x von p (das ein Zeiger ist, keine Struktur) und dereferenziere das. Du brauchst (*p).x oder schreibst einfach p->x.

Sollte ich eine Struktur in C als Wert oder per Zeiger übergeben?

Übergib per Zeiger, wenn die Funktion die Struktur des Aufrufers ändern muss oder wenn die Struktur groß ist und das Kopieren verschwenderisch wäre. Übergib kleine, nur lesend benutzte Strukturen als Wert, wo die Kopie billig und der Code einfacher ist. Markiere nur lesende Zeigerparameter als const struct T *, damit der Compiler das Versprechen durchsetzt.

Wie belegt man eine Struktur in C mit malloc?

struct Node *n = malloc(sizeof *n); - die Form sizeof *n fragt nach der Größe dessen, worauf n zeigt, sie bleibt also korrekt, wenn du später den Typ änderst. Prüfe das Ergebnis gegen NULL, initialisiere jedes Element und gib es am Ende mit free(n) frei.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S