Frage

Wie kann ich verstehen folgende komplizierte Erklärungen?

char (*(*f())[])();

char (*(*X[3])())[5];

void (*f)(int,void (*)()); 

char far *far *ptr;

typedef void (*pfun)(int,float);

int **(*f)(int**,int**(*)(int **,int **));
War es hilfreich?

Lösung

Wie andere haben darauf hingewiesen, cdecl ist das richtige Werkzeug für den Job.

Wenn Sie diese Art von Erklärung von cdecl ohne Hilfe zu verstehen, versuchen Sie von innen Auslesen und rechts nach links

Unter einem zufälligen Beispiel aus der Liste char (*(*X[3])())[5];
Beginnen Sie an X, der die Kennung erklärt wird / definiert (und der innersten Identifier):

char (*(*X[3])())[5];
         ^

X heißt

X[3]
 ^^^

X gleich ein Feld von 3

(*X[3])
 ^                /* the parenthesis group the sub-expression */

X ist ein Array von 3 Zeigern auf

(*X[3])()
       ^^

X ist ein Array von Zeigern auf 3 Funktion eine nicht spezifiziert (aber feste) Anzahl der Argumente

Annehmen
(*(*X[3])())
 ^                   /* more grouping parenthesis */

X ist ein Array von 3 Zeiger auf Funktion, um eine nicht näher bezeichnete (aber festen) Anzahl der Argumente zu akzeptieren und Zurückgeben eines Zeigers

(*(*X[3])())[5]
            ^^^

X ist ein Array von 3 Zeiger auf Funktion, um eine nicht näher bezeichnete (aber festen) Anzahl der Argumente, die Annahme und Rückgabe eines Zeigers auf ein Array von 5

char (*(*X[3])())[5];
^^^^                ^

X ist ein Array von 3 Zeigern auf Funktion, um eine nicht näher bezeichnet (aber feste) Anzahl der Argumente zu akzeptieren und einen Zeiger auf ein Feld von 5 zurückzukehr char .

Andere Tipps

Lesen Sie es von innen nach außen, ähnlich wie man Gleichungen wie {3+5*[2+3*(x+6*2)]}=0 lösen würde - würden Sie beginnen mit der Lösung, was in () ist dann [] und schließlich {}:

char (*(*x())[])()
         ^

Das bedeutet, dass x ist etwas .

char (*(*x())[])()
          ^^

x ist eine Funktion .

char (*(*x())[])()
        ^

x gibt einen Zeiger auf etwas .

char (*(*x())[])()
       ^    ^^^

x gibt einen Zeiger auf ein Array .

char (*(*x())[])()
      ^

x gibt einen Zeiger auf ein Array von Zeigern .

char (*(*x())[])()
     ^         ^^^

x gibt einen Zeiger auf ein Array von Zeigern auf Funktionen

char (*(*x())[])()
^^^^

Bedeutung des Arrays von Zeigern x auf ein Array von Zeigern, die Funktion zurück Punkt auf Funktionen, die a char zurück.

Aber ja, benutzen cdecl . Ich habe es mir meine Antwort zu überprüfen.)

Wenn dies verwirrend Sie noch (und es sollte wahrscheinlich), versucht, die gleiche Sache auf einem Stück Papier zu tun oder in Ihrem bevorzugten Texteditor. Es gibt keine Möglichkeit zu wissen, was es bedeutet nur, indem es bei der Suche.

Klingt wie ein Job für das cdecl Werkzeug:

cdecl> explain char (*(*f())[])();
declare f as function returning pointer to array of pointer to function returning char

Ich sah mich um für eine offizielle Homepage für das Werkzeug, aber konnte man nicht finden, die echt zu sein schienen. Unter Linux können Sie in der Regel erwarten, dass Ihre Verteilung der Wahl um das Werkzeug zu schließen, so dass ich es gerade installiert, um die obige Probe zu erzeugen.

Sie sollten verwenden cdecl Tool. Es sollte auf den meisten Linux-Distributionen zur Verfügung.

z. für diese Funktion, wird es Sie zurück:

char (*(*f())[])(); - festzustellen, wie f Funktionszeiger auf Array von Zeiger zurückkehr char funktioniert Rückkehr

void (*f)(int,void (*)()); - Prototyp des Funktionszeiger f. f eine Funktion ist, die zwei Parameter nimmt, die erste ist int, und das zweite ist ein Funktionszeiger für eine Funktion, die Lücke zurückgibt.

char far *far *ptr; - ptr ist ein weiter Zeiger zu einem weit Zeiger (die zu einem gewissen char Punkten /Byte).

char (*(*X[3])())[5]; - X ist ein Array von Zeigern 3 eine undeterminate Anzahl von Argumenten funktionieren zu akzeptieren und einen Zeiger auf ein Feld von 5 char Rückkehr

.

typedef void (*pfun)(int,float); - erklärt Funktionszeiger PFUN. PFUN a fuctnion ist, die zwei Parameter nimmt, int erste ist, ist der zweite Schwimmer-Typ. hat die Funktion keinen Rückgabewert hat;

z.

void f1(int a, float b)
{ //do something with these numbers
};

Btw, komplizierte Erklärungen wie die letzten nicht oft gesehen. Hier ist ein Beispiel, das ich nur für diesen Zweck aus.

int **(*f)(int**,int**(*)(int **,int **));

typedef int**(*fptr)(int **,int **);

int** f0(int **a0, int **a1)
{
    printf("Complicated declarations and meaningless example!\n");
    return a0;
}

int ** f1(int ** a2, fptr afptr)
{
    return afptr(a2, 0);
}

int main()
{
    int a3 = 5;
    int * pa3 = &a3;
    f = f1;
    f(&pa3, f0);

    return 0;
}

Es scheint, dass Ihre eigentliche Frage lautet:

  

Was ist der Anwendungsfall für einen Zeiger auf einen Zeiger?

Ein Zeiger auf einen Zeiger neigt zu zeigen, wenn Sie haben eine Reihe von irgendeiner Art T und T selbst ist ein Zeiger , um etwas anderes. Zum Beispiel:

  • Was ist eine Zeichenfolge in C? Typischerweise ist es ein char *.
  • Möchten Sie ein Array von Strings von Zeit zu Zeit gerne? Sicher.
  • Wie würden Sie einen erklären? char *x[10]. x ist ein Array von 10 Zeigern auf char, aka 10 Saiten

An diesem Punkt könnte man sich fragen, wo char ** kommt. Es geht um das Bild von der sehr engen Beziehung zwischen Zeigern Arithmetik und Arrays in C. Ein Array-Name, x ist (fast) immer in einen Zeiger umgewandelt, um es erste ist Element.

  • Was ist das erste Element? Ein char *.
  • Was ist ein Zeiger auf das erste Element? Ein char **.

C, array E1[E2] wird definiert *(E1 + E2) äquivalent. Normalerweise E1 ist der Array-Name, sagen wir mal x, die automatisch zu einem char ** umgewandelt und E2 einige Index, sagen 3. (Diese Regel erklärt auch, warum 3[x] und x[3] sind dasselbe.)

Zeiger auf Zeiger zeigen auch auf, wenn Sie eine dynamisch zugewiesene Array eines Typs T wollen, die selbst einen Zeiger . So starten Sie mit, lassen Sie uns so tun, als wir nicht wissen, welche Art T ist.

  • Wenn wir einen dynamisch zugewiesenen Vektor von T-Shirts wollen, welche Art brauchen wir? T *vec.
  • Warum? Weil wir in Zeigerarithmetik C durchführen kann, eine beliebige T * kann als die Basis einer zusammenhängenden Sequenz von T des im Speicher dienen.
  • Wie ordnen wir diesen Vektor, sagen von n Elemente? vec = malloc(n * sizeof(T));

Diese Geschichte für absolut jede Art T wahr ist, und es ist so wahr für char *.

  • Was ist die Art von vec wenn T char * ist? char **vec.

Zeiger auf Zeiger zeigen auch auf, wenn Sie haben eine Funktion, die ein Argument vom Typ T, selbst ein Zeiger ändern muss.

  • Schauen Sie sich die Erklärung für strtol. long strtol(char *s, char **endp, int b)
  • Was ist das alles? strtol wandelt einen String aus Basis b auf eine ganze Zahl. Es will Ihnen sagen, wie weit in den String es. Es könnte vielleicht eine Struktur zurückkehren sowohl eine long und char * enthält, aber das ist nicht, wie es erklärt.
  • Stattdessen gibt es die zweite Folge von in der Adresse vorbei von einer Schnur, die sie vor der Rückkehr modifiziert.
  • Was ist wieder ein String? Ach ja, char *.
  • Was ist also eine Adresse eines Strings? char **.

Wenn Sie diesen Pfad wandern nach unten lange genug, können Sie auch in T *** Typen laufen, obwohl man fast immer den Code neu strukturieren kann, sie zu vermeiden.

Schließlich Zeiger auf Zeiger erscheinen in bestimmten tricky Implementierungen von verknüpften Listen . Betrachten Sie die Standard-Deklaration einer doppelt verketteten Liste in C.

struct node {
    struct node *next;
    struct node *prev;
    /* ... */
} *head;

Das funktioniert gut, obwohl ich nicht die Einfügen / Löschen Funktionen hier reproduzieren, aber es hat ein kleines Problem. Jeder Knoten kann aus der Liste entfernt werden (oder einen neuen Knoten eingefügt, bevor es), ohne den Kopf der Liste zu verweisen. Nun, nicht ganz jeder Knoten. Dies gilt nicht für das erste Element der Liste, wo prev null sein wird. Dies kann in einigen Arten von C-Code mäßig ärgerlich sein, wo man mehr mit den Knoten arbeiten, um sich als mit der Liste als einenKonzept. Dies ist ein recht häufiges Vorkommen in Low-Level-Systemen Code.

Was passiert, wenn wir umschreiben node wie folgt aus:

struct node {
    struct node *next;
    struct node **prevp;
    /* ... */
} *head;

In jedem Knoten prevp Punkte nicht an dem vorherigen Knoten, aber am next Zeiger des vorherigen Knoten. Was ist mit dem ersten Knoten? Es ist prevp Punkte bei head. Wenn Sie eine Liste wie diese ziehen (und Sie wurde zu ziehen, um zu verstehen, wie das funktioniert) sehen Sie, dass Sie das erste Element entfernen oder einen neuen Knoten vor dem ersten Element einzufügen, ohne explizit Referenzierung head mit Namen.

  

x: Funktion Rückkehr Zeiger auf   array [] der Zeiger auf Funktion   Rückkehr char“- huh

Sie haben eine Funktion

Diese Funktion gibt einen Zeiger.

, die Zeiger auf ein Array.

Das Array ist ein Array von Funktionszeigern (oder Zeiger auf Funktionen)

Diese Funktionen geben char *.

 what's the use case for a pointer to a pointer?

Eine davon ist der Rückgabewert durch Argumente zu erleichtern.

Nehmen wir an Sie haben

int function(int *p)
  *p = 123;
   return 0; //success !
}

Sie nennen es wie

int x;
function(&x);

Wie Sie sehen können, für function der Lage sein, unsere x zu modifizieren wir haben es einen Zeiger auf unsere x zu übergeben.

Was passiert, wenn x kein int, sondern ein char *? Nun, es ist immer noch die gleichen, müssen wir einen Zeiger auf das passieren. Ein Zeiger auf einen Zeiger:

int function(char **p)
  *p = "Hello";
   return 0; //success !
}

Sie nennen es wie

char *x;
function(&x); 

Remo.D Antwort für Funktionen zu lesen ist ein guter Vorschlag. Hier sind einige Antworten auf die anderen.

Ein Anwendungsfall für einen Zeiger auf einen Zeiger, wenn Sie es an eine Funktion zu übergeben wollen, dass der Zeiger ändern wird. Zum Beispiel:

void foo(char **str, int len)
{
   *str = malloc(len);
}

Auch dies könnte ein Array von Strings sein:

void bar(char **strarray, int num)
{
   int i;
   for (i = 0; i < num; i++)
     printf("%s\n", strarray[i]);
}

Normalerweise ein sollte nicht Verwendung Erklärungen diese komplizierte, wenn auch manchmal Typen, die Sie brauchen, die für Dinge wie Funktionszeiger ziemlich kompliziert sind. In diesen Fällen ist es viel besser lesbar typedefs für Zwischentypen zu verwenden; zum Beispiel:

typedef void foofun(char**, int);
foofun *foofunptr;

Oder für Ihr erstes Beispiel „Funktionszeiger auf Array Rückkehr [] des Zeigers Rückkehr char zu funktionieren“, könnten Sie tun:

typedef char fun_returning_char();
typedef fun_returning_char *ptr_to_fun;
typedef ptr_to_fun array_of_ptrs_to_fun[];
typedef array_of_ptrs_to_fun *ptr_to_array;
ptr_to_array myfun() { /*...*/ }

In der Praxis, wenn Sie schreiben etwas gesund, werden viele dieser Dinge haben aussagekräftige Namen ihrer eigenen; zum Beispiel könnte diese Funktionen Rückkehr Namen sein (irgendeiner Art), so fun_returning_char name_generator_type werden konnte und array_of_ptrs_to_fun name_generator_list werden konnte. So könnte man es ein paar Zeilen zusammenbrechen, und nur diese beiden typedefs definieren -., Das wahrscheinlich nützlich anderswo in jedem Fall sein wird

char far *far *ptr;

Dies ist eine veraltete Microsoft Form, zurückgehend auf MS-DOS und sehr frühe Windows-Tage. Die kurze Version ist, dass dies zu einem weit Zeiger auf ein char ein weiter Zeiger ist, wo ein weiter Zeiger überall im Speicher zeigen kann, wie zu einem in der Nähe Zeigern gegenüber, die nur irgendwo in 64K Datensegment zeigen könnten. Sie wollen wirklich nicht die Details zu Microsoft Speichermodelle wissen, für die Arbeit rund um die völlig hirntoten Intel 80x86-Speicherarchitektur segmentiert.

typedef void (*pfun)(int,float);

Dies erklärt hiermit als typedef für einen Zeiger auf eine Prozedur Pfun, die einen int und einen Schwimmer nimmt. Normalerweise würden Sie diese in einer Funktionsdeklaration verwenden oder einen Prototyp, nämlich.

float foo_meister(pfun rabbitfun)
{
  rabbitfun(69, 2.47);
}

Wir müssen alle Aussagen Zeigerdeklaration bewerten von links nach rechts, ausgehend von dem der Zeiger Name oder Erklärung Name auf der Anweisung deklariert wird.

Während die Erklärung der Bewertung müssen wir von der innersten Klammer beginnen.

Beginnen Sie mit dem Zeigernamen oder Funktionsnamen durch die Zeichen ganz rechts in der parenthersis gefolgt und dann von den am weitesten links stehenden Zeichen follwed.

Beispiel:

char (*(*f())[])();
         ^

char (*(*f())[])();
         ^^^
In here f is a function name, so we have to start from that.

f ()

char (*(*f())[])();
            ^
Here there are no declarations on the righthand side of the current
parenthesis, we do have to move to the lefthand side and take *:

char (*(*f())[])();
      ^
f() *

Wir haben die innere Klammer Zeichen abgeschlossen, und jetzt haben wir auf einer Ebene dahinter zurück:

char (*(*f())[])();

   ------

Nun nehmen Sie [], weil diese auf der rechten Seite der aktuellen Klammer ist.

char (*(*f())[])();
             ^^

f() * []

Nun ist das nehmen * weil es keine Zeichen auf der rechten Seite.

char (*(*f())[])();
               ^

char (*(*f())[])();
      ^
f() * [] *

char (*(*f())[])();

Als nächstes bewertet die äußere offene und schließende Klammer, eine Funktion ist angibt.

f() * [] * ()

char (*(*f())[])();

Jetzt können wir Datentyp am Ende der Anweisung hinzuzufügen.

f() * [] * () char.

char (*(*f())[])();

Endgültige Antwort:

    f() * [] * () char.

f ist eine Funktion Zeiger auf Array [] von Zeigern Rückkehr char funktionieren zurück.

Vergessen Sie 1 und 2 -. Dies ist nur theoretische

3: Dies ist in der Programmeingabe-Funktion int main(int argc, char** argv) verwendet. Sie können eine Liste von Strings zugreifen, indem Sie einen char** verwenden. argv [0] = erste String argv [1] = zweiter String, ...

auf eine Funktion einen Zeiger als Argument übergeben können diese Funktion den Inhalt der Variablen darauf ändern, die für die Rückkehr Informationen durch andere Mittel als die Funktion Rückgabewert nützlich sein kann. Zum Beispiel könnte der Rückgabewert bereits verwendet werden, Fehler / Erfolg anzuzeigen, oder Sie können mehrere Werte zurückgeben möchten. Die Syntax für diese in der aufrufende Code ist foo (& var), die die Adresse des var nimmt, das heißt, einen Zeiger auf var.

So als solche, wenn die Variable, deren Inhalt Sie die Funktion zu ändern, ist selbst ein Zeiger (zum Beispiel ein String), würde der Parameter als Zeiger auf einen Zeiger deklariert werden .

#include <stdio.h>

char *some_defined_string = "Hello, " ; 
char *alloc_string() { return "World" ; } //pretend that it's dynamically allocated

int point_me_to_the_strings(char **str1, char **str2, char **str3)
{
    *str1 = some_defined_string ;
    *str2 = alloc_string() ;
    *str3 = "!!" ;

    if (str2 != 0) {
        return 0 ; //successful
    } else {
        return -1 ; //error
    }
}

main()
{
    char *s1 ; //uninitialized
    char *s2 ;
    char *s3 ;

    int success = point_me_to_the_strings(&s1, &s2, &s3) ;

    printf("%s%s%s", s1, s2, s3) ;
}

Beachten Sie, dass main () zuteilen keine Lagerung für die Saiten, so point_me_to_the_strings () schreibt nicht zu STR1, STR2 und str3 wie es wäre, wenn sie als Zeiger auf Zeichen übergeben wurden. Vielmehr point_me_to_the_strings () ändert den Zeiger selbst, sie verweisen auf verschiedene Orte zu machen, und es kann dies tun, weil es Hinweise auf sie hat.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top