Menu

typedef in C: Typen benennen, das typedef-struct-Idiom und wann man es sein lässt

Wie typedef einem vorhandenen C-Typ einen neuen Namen gibt - das typedef-struct-Idiom mit und ohne Tag, typedefs für Funktionszeiger und Arrays, und der eine Fall, in dem ein typedef schadet: einen Zeiger zu verstecken.

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

typedef tut eine kleine Sache: Es gibt einem bereits vorhandenen Typ einen weiteren Namen. Es wird kein neuer Typ erzeugt, kein Speicher beiseitegelegt, und nichts daran, wie sich der Wert verhält, ändert sich. Es ist ein Benennungswerkzeug - und in C, wo die eingebauten Typnamen lang werden (unsigned long long, struct Employee, void (*)(int)), ist ein guter Name viel wert.

Die Grundform

Die Syntax liest sich wie eine Variablendeklaration mit vorangestelltem typedef. Dort, wo der Variablenname stünde, steht der neue Typname:

unsigned long  count;           // deklariert eine Variable namens count
typedef unsigned long  ulong;   // deklariert einen TYP namens ulong

Das ist die ganze Regel, und sie erklärt jedes typedef, das dir je begegnet, einschließlich der seltsam aussehenden weiter unten auf dieser Seite.

Beachte, was das Paar Celsius/Fahrenheit dir nicht bringt: Typsicherheit. Beide sind schlicht double, einen Fahrenheit-Wert dort zu übergeben, wo Celsius erwartet wird, kompiliert also bereitwillig. Die Namen dokumentieren die Absicht für Menschen; sie erzeugen keine Prüfung.

Das typedef-struct-Idiom

Das ist der Grund, warum die meisten C-Programme überhaupt ein typedef enthalten. In C schließt der Typname einer Struktur das Schlüsselwort ein:

struct Point { int x; int y; };
struct Point p;              /* das Wort "struct" jedes einzelne Mal */

Ein typedef fasst das zusammen:

Lies es mit der Regel von vorhin: Streiche das typedef, und du hast struct Point {...} Point;, die Deklaration einer Variablen namens Point. Setze typedef wieder davor, und Point wird stattdessen zu einem Typnamen.

Mit Tag oder ohne

Du kannst das Tag ganz weglassen, was eine anonyme Struktur nur mit dem typedef-Namen ergibt:

typedef struct {
    int x;
    int y;
} Point;          /* ein "struct Point" existiert nicht - nur "Point" */

Das ist ordentlicher, und für einen schlichten Datensatz ist es der übliche Stil. Es scheitert aber in dem Moment, in dem die Struktur sich selbst nennen muss, denn der typedef-Name ist erst benutzbar, wenn seine eigene Deklaration abgeschlossen ist:

/* Kompiliert NICHT: "Node" ist innerhalb seiner eigenen Klammern noch kein Typ. */
typedef struct {
    int value;
    Node *next;
} Node;

Behalte das Tag, und die Selbstreferenz funktioniert, denn struct Node ist benutzbar, sobald das Tag gesehen wurde:

Die praktische Regel: Behalte das Tag. Es kostet ein Wort, es stimmt mit dem typedef-Namen überein, sodass nichts verwirrt, und es hält die Tür für Selbstreferenzen und Vorwärtsdeklarationen in Headern offen. Siehe Strukturen und Zeiger, wozu dieser verkettete Knoten heranwächst.

Vorwärtsdeklarationen und undurchsichtige Typen

Weil ein Zeiger auf eine Struktur auch dann eine bekannte Größe hat, wenn der Inhalt der Struktur unbekannt ist, kann ein Header einen Typ herausgeben, ohne seine Elemente preiszugeben:

/* stack.h */
typedef struct Stack Stack;     /* deklariert, nicht definiert */

Stack *stack_create(void);
void   stack_push(Stack *s, int value);
int    stack_pop(Stack *s);
void   stack_destroy(Stack *s);

Das vollständige struct Stack { ... }; lebt in stack.c, und niemand von außen kann seine Elemente anfassen. Dieses Muster des undurchsichtigen Typs ist die Art, wie C-Bibliotheken Kapselung durchsetzen, und es ist genau das, was FILE in der Standardbibliothek ist - du bekommst ein FILE * von fopen und erfährst nichts darüber, was darin steckt. Siehe Dateiverarbeitung.

typedef für enums und unions

Dasselbe Idiom gilt für die beiden anderen zusammengesetzten Arten:

Ohne das typedef wären das enum Color c; und union Number n;. Siehe Enums und Unions, was sie tatsächlich tun.

typedef für einen Funktionszeiger

Hier hört typedef auf, eine Bequemlichkeit zu sein, und wird beinahe notwendig. Die rohe Syntax eines Funktionszeigers ist berüchtigt schwer zu lesen:

void (*handler)(int, const char *);                /* eine Variable */
void register_cb(void (*cb)(int, const char *));   /* ein Parameter */

Benenne den Typ einmal, und beide werden lesbar:

Wende die Streiche-das-typedef-Regel erneut an: int (*IntBinaryOp)(int, int); allein würde eine Variable namens IntBinaryOp deklarieren, die auf eine Funktion zeigt, die zwei ints nimmt und ein int liefert. Mit typedef wird daraus der Name des Typs. Mehr unter Funktionszeiger.

typedef gegen #define

Sie sehen bei einfachen Aliasen austauschbar aus und sind es nicht:

#define INT_PTR int *
typedef int *int_ptr;

INT_PTR  a, b;    /* expandiert zu: int *a, b;  -- b ist ein schlichtes int! */
int_ptr  c, d;    /* sowohl c als auch d sind int *  */

#define ist blinde Textersetzung, die vor dem Compiler passiert; typedef ist eine echte Deklaration, die der Compiler versteht, und sie gilt für jeden Namen in der Liste. Nimm typedef für Typen und behalte #define für Konstanten und Makros.

Wann ein typedef schadet

Typedefs können auch Information verbergen, die der Leser braucht. Das schärfste Beispiel ist das Verstecken eines Zeigers:

typedef struct Node *NodeRef;    /* der Stern verschwindet */

NodeRef n = get_node();

Beim Anblick von NodeRef n kann ein Leser nicht sagen, ob n NULL sein kann, ob es freigegeben werden muss, ob n und eine Kopie davon dasselbe Objekt teilen oder ob Elemente mit . oder -> erreicht werden. Alle vier Fragen beantwortet struct Node *n sofort.

Die Standardbibliothek stimmt zu: Sie benennt die Struktur per typedef (FILE) und lässt den Stern an der Verwendungsstelle (FILE *fp). Zwei vernünftige Leitlinien folgen daraus:

  • Benenne Strukturen, Unions, Enums und Funktionszeiger per typedef - das entfernte Rauschen trägt keine Information.
  • Benenne keinen Zeiger per typedef, nur um ihn zu verkürzen, und benenne keinen Basistyp, um ihn zu verkleiden (typedef int Boolean; lädt jemanden ein, eine 7 darin zu speichern - nimm stattdessen bool aus <stdbool.h>, behandelt unter Booleans).

Die Namen fester Breite der Standardbibliothek in <stdint.h> (uint32_t, int64_t, size_t) sind alle typedefs und das beste Argument für dieses Sprachmittel: Sie sagen genau, was sie sind, sie sind portabel, und nichts wird versteckt.

Häufig gestellte Fragen

Was macht typedef in C?

Es gibt einem vorhandenen Typ einen zweiten Namen. typedef unsigned long ulong; bedeutet, dass ulong und unsigned long von da an derselbe Typ sind. Es erzeugt keinen neuen Typ und keinen neuen Speicher - nur eine kürzere oder aussagekräftigere Schreibweise für einen, den du schon hast.

Was ist das typedef-struct-Idiom in C?

typedef struct Point { int x; int y; } Point; deklariert die Struktur und benennt den Typ Point in einer Anweisung, sodass du Point p; statt struct Point p; schreiben kannst. Das Tag (struct Point) zu behalten zählt, wenn die Struktur sich selbst nennen muss, wie ein Knoten einer verketteten Liste.

Was ist der Unterschied zwischen typedef und #define?

typedef wird vom Compiler behandelt und erzeugt einen echten Typalias; #define ist eine Textersetzung, die der Präprozessor vor der Übersetzung vornimmt. Dieser Unterschied beißt bei Zeigern: #define PTR int* lässt PTR a, b; zu int* a, b; expandieren - nur a ist ein Zeiger. typedef int *PTR; macht beide zu Zeigern.

Sollte ich in C einen Zeigertyp per typedef benennen?

Meist nicht. typedef struct Node *NodeRef; versteckt, dass der Typ ein Zeiger ist, Leser können also nicht erkennen, ob eine Variable NULL sein kann, freigegeben werden muss oder -> braucht. Das FILE * der Standardbibliothek lässt den Stern genau aus diesem Grund sichtbar. Benenne die Struktur per typedef, nicht den Zeiger darauf.

Coddy programming languages illustration

Lerne mit Coddy zu programmieren

LOS GEHT'S