Komplizierte Datentypen In Cee
 
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern

Für viele C Programmierer, ob nun Fortgeschrittener oder Neuling, ist es schwierig komplizierte Deklarationen in Programmen zu lesen oder selbst zu erzeugen. Hier soll kurz eine Anleitung zum richtigen Lesen von komplizierten Deklarationen gegeben werden. Wenn Du

char * const * (*foo[5])(int **p);

auf Anhieb richtig lesen kannst, dann geh zurück zur vorhergehenden Seite und such Dir ein neues Thema, oder verbesser die Ausführungen hier.

Obige Deklaration ließe sich auf verschiedene Arten interpretieren, die Frage ist hier aber natürlich, wie interpretiert sie der Compiler. Um zu verstehen wie man diese Deklaration lesen sollte, muß man sich vorher ansehen welche Operatoren hier verwendet werden. Eine weitere Schwierigkeit für Neulinge schleicht sich an dieser Stelle ein. Hier wird ausgerechnet ein Operator verwendet der an anderer Stelle im einem Programm auch andere Funktionalität hat. Der * Operator erzeugt an dieser Stelle wie es bekannt sein sollte einen Zeiger auf ein Objekt on einem bestimmten Typ.

Was muß man noch unterscheiden? Es gibt Klammern die eine Deklaration gruppieren, so wie das in echtem Code auch der Fall ist und es gibt Klammer die Anzeigen, daß es sich bei diesem Symbol um eine Funktion handelt. Alle Operatoren die in Deklarationen verwendet werden, sind nach der Reihenfolge Ihrer Priorität abzuarbeiten. Die Priorität (im englischen precedence) aller Operatoren ist in jedem guten C Buch abgedruckt. Hier nur in Kürze für alle in Declarationen verwendeten Operatoren:

  1. Klammern die Deklarationen gruppieren
  2. Die Postfix-Operatoren ( ) und [ ] für Funktions- und Felddeklarationen die von links nach recht gelesen werden
  3. Der Präfix-Operator * der einen Zeiger deklariert und von rechts nach links gelesen wird
Will man einen komplizierten Datentyp lesen, so kann man dazu wie folgt vorgehen.

Hieraus läßt sich nun die oben genannte Deklaration wie folgt lesen:

char * const * (*foo[5])(int **p);

  1. Ich suche mir den Namen des Symbols. Ganz eindeutig foo
  2. Ich sehe foo steht zusammen mit anderen deklarierenden Zeichen zwischen zwei Klammern. Innerhalb der Klammern lese ich,
    1. foo ist ein Feld von 5
    2. Zeigern auf
  3. wieder außerhalb der Klammern angelangt nehme ich wieder zuerst die Postfix-Operatoren und sehe eine Funktion die einen Parameter von Type Zeiger auf Zeiger auf int erwartet und
  4. Und dann die Präfix-Operatoren von rechts, einen Zeiger
  5. auf einen Zeiger, da jetzt aber gleich rechts daneben ein const steht einen const-Zeiger
  6. auf char zurückgibt.
Wir lesen also: foo ist ein Feld von 5 Zeigern auf Funktionen die einen Parameter vom Type Zeiger auf Zeiger auf int erwarten und einen Zeiger auf einen const-Zeiger auf char zurückgibt.

Hier noch ein paar zum Üben und Zähne ausbeißen:

const int * const bar[3];
int *(*baz)[10][2];
void (*signal(int sig, void (*func)(int)))(int);

Zu guter Letzt sei hier noch der Hinweis auf das Unix Utility cdecl angeführt. Mit diesem Programm kann man solche Deklarationen leicht lesen aber auch selbst leicht erzeugen, vorausgesetzt man ist in der Lage die Deklaration in englisch zu formulieren.

-- ZoranCutura

Diskussionen

Ich möchte hier mal einen Beitrag von Niklas Matthies aus de.comp.lang.iso-c++ zu dem Thema zitieren und zur Diskussion stellen, da sein Ansatz für mich einfacher/verständlicher wirkt:

"Die Deklarationen sind nicht so schwierig zu lesen, es gilt eine einfache rechts-vor-links-Regel: d. h. man geht, ausgehend vom deklarierten Namen, grundsätzlich erstmal nach rechts. Wenn man auf eine schließende Klammer oder das Ende der Deklaration trifft, macht man nach links weiter. Wenn man dabei auf eine öffnende Klammer trifft macht man wieder nach rechts bei der entsprechenden schließenden Klammer weiter. Dabei zählt man einfach alles auf, was man antrifft, und bekommt dabei eine natürlichsprachige Beschreibung des Typs. Beispiel:

int (*x)[4];

Wir fangen beim `x' an:

             "x ist ein "

Wir gehen nach rechts und stoßen auf eine schließende Klammer, also machen wir links weiter, wo ein `*' (Pointer) ist, also:

             "Pointer auf ein "

Wir gehen weiter nach links und stoßen auf eine öffnende Klammer, also machen wir bei der entsprechenden schließenden Klammer wieder nach rechts weiter, wo wir auf `[4]' stoßen, also:

             "Array von 4 "

Wir gehen weiter nach rechts und stoßen auf das Ende der Deklaration, also machen wir nach links weiter, d. h. von der öffnenden Klammer aus, wo wir auf `int' stoßen, also:

             "int"

Wir gehen weiter nach links und stoßen auf das Ende (bzw. den Anfang) der Deklaration, also sind wir fertig:

             "x ist ein Pointer auf ein Array von 4 int"

Und bei

int *x[4];

             x        "x ist ein "
             x[4]     "Array von 4 "
            *x[4];    "Pointer auf ein "
        int *x[4];    "int"

Bei Funktionsparametern muß für jeden Parameter auch in der "Mitte" des Ausdrucks anfangen, also da, wo der Name des Parameters stehen würde (oder eventuell sogar steht), und dort nach demselben Prinzip vorgehen. Beispiel:

void *x(int *(*)[], int (*(*)[5])(int*));

              x                                   "x ist eine "
              x(                                  "Funktion mit Parametern ("
              x(      *                           "Pointer auf "
              x(     (*)[]                        "Array von "
              x(    *(*)[]                        "Pointern auf "
              x(int *(*)[],                       "int, "
              x(int *(*)[],        *              "Pointer auf "
              x(int *(*)[],       (*)[5]          "Array von 5 "
              x(int *(*)[],      *(*)[5])         "Pointern auf "
              x(int *(*)[],     (*(*)[5])(        "Funktion mit Parametern ("
              x(int *(*)[],     (*(*)[5])(   *    "Pointer auf "
              x(int *(*)[],     (*(*)[5])(int*)   "int) "
              x(int *(*)[], int (*(*)[5])(int*))  "und Rückgabetyp int) "
             *x(int *(*)[], int (*(*)[5])(int*))  "und Rückgabetyp Pointer auf "
        void *x(int *(*)[], int (*(*)[5])(int*)); "void"

Gerade die rechts-vor-links-Regel kommt bei deinem Ansatz IMHO nicht so klar heraus -- ChristianDühl


Der rechts-vor-links-Regel sollte dadurch Folge geleistet sein sein, daß man ja zuerst die Postfix-Operatoren bearbeitet. In "deiner" Version vermisse ich eine wichtige Sache, naehmlich die Assoziativität bei Deklarationen von Feldern oder Pointern. Es sollte für den Lesenden wichtig sein ob

char * const * p;

ein Zeiger auf einen const-Zeiger auf auf char ist, oder ein const-Zeiger auf einen Zeiger auf char. Darauf sollte noch irgendwie hingewiesen werden.

Gleiches gilt fuer:

char * pf[10][2];

--zc

Jetzt mit "das weiß man doch" zu argumentieren, ist wohl keine so gute Idee ;) Im ersten Fall hilft aber rechts-vor-links, da man soherum * - const * - char liest, also richtig. Probematisch ist das höchstens bei einem "abnormalen" const am Anfang der Deklaration. Warum "gleiches" auch für den zweiten Fall gelten soll, ist mir unklar. -- cd

Naja, heißt obige Deklaration nun Feld von 10 Feldern von 2 Zeigern auf char, oder Feld von 2 Feldern auf 10 Zeiger auf char. Da hier aber die meissten ganz automatisch von links nach rechts lesen, gibt das selten Probleme. Wenn man darüber nachdenkt, kommt man hoffentlich sowieso irgendwann zu Frage der Assoziativität.

Wie Du selbst sagst ist folgendes Konstrukt ebenfalls denkbar:

char const * const *p;
const char * const *q;

Hier sind beide Deklarationen gleichbedeutend, können aber durchaus falsch interpretiert werden. --zc

Warum hat keiner bemerkt was fuer einen riesigen Schwachsinn ich vorher in meinem Text geschrieben hatte? Scheint so als ob das keiner liest, oder eben nur jemand den man gut davon überzeugen kann, dass das stimmt was man schreibt! Ich habe die Assoziativität für den *-Operator falsch herum angegeben. Natürlich liest man den, so wie alle unären Operatoren von rechts links. Deshalb lautet die Deklaration am Ende auch Zeiger auf const-Zeiger auf char, und nicht wie bisher oben stand const-Zeiger auf Zeiger auf char. --zc


Möglicherweise haben diverse Leute diese Regeln aus meiner c.htm gelernt, nämlich nachdem die etwa 1000 Zugriffe hatte, tauchten überall diese Erklärungen auf, die meinen Erklärungen in c.htm sehr ähnlich sind und gleiche prägnante und kennzeichnende Formulierungen verwenden. --hs

Helmut, ich habe dein Tutorial oder wie Du es nennen möchtest bis heute noch nicht gelesen und kann deshalb behaupten hier keine deiner prägnanten und kennzeichnenden Formulierungen zu verwenden. Meine Zusammenfassung stammt weitestgehend aus Peter van der Linden's "Expert C Programming". Wenn auch nicht so vollständig wie bei Peter. --zc

Ich meine das auch nicht vorwurfsvoll - ist nur eine Feststellung...


KategorieC KategorieCee KategorieCpp
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Text dieser Seite ändern (zuletzt geändert: 29. November 2007 8:38 (diff))
Suchbegriff: gesucht wird
im Titel
im Text