C ist nicht so schwer: void ( *( *f[] ) () ) ()

Lesezeit: 11 Minuten

Benutzeravatar von Motun
Motun

Ich habe heute ein Bild gesehen und denke, ich wäre für Erklärungen dankbar. Hier also das Bild:

etwas C-Code

Ich fand das verwirrend und fragte mich, ob solche Codes jemals praktikabel sind. Ich habe das Bild gegoogelt und ein anderes Bild darin gefunden Dies reddit-Eintrag, und hier ist das Bild:

eine interessante Erklärung

Also ist dieses “Spirallesen” etwas Gültiges? Analysieren C-Compiler so?
Es wäre großartig, wenn es einfachere Erklärungen für diesen seltsamen Code gäbe.
Abgesehen davon, können diese Art von Codes nützlich sein? Wenn ja, wo und wann?

Es gibt eine Frage zur “Spiralregel”, aber ich frage nicht nur, wie sie angewendet wird oder wie Ausdrücke mit dieser Regel gelesen werden. Ich bezweifle auch die Verwendung solcher Ausdrücke und die Gültigkeit der Spiralregel. Zu diesen wurden bereits einige nette Antworten gepostet.

  • Wie könnte die Erklärung einfacher sein? Es deckt alle Aspekte der Definition von fw/ ein paar Wörter für jeden Schlüsselpunkt ab.

    – Scott Jäger

    31. Dezember 2015 um 16:07 Uhr

  • Vielleicht ist C schwer? Das erklärt tatsächlich f als Array von Zeigern auf Funktionen, die jedes Argument annehmen könnten … wenn es so wäre void (*(*f[])(void))(void);dann ja, es wären Funktionen, die keine Argumente annehmen …

    – txtechhelp

    31. Dezember 2015 um 16:13 Uhr


  • Codieren Sie in der Praxis keinen solchen obskuren Code. Verwenden Sie typedef für Signaturen

    – Basile Starynkevitch

    31. Dezember 2015 um 18:21 Uhr

  • Jede Deklaration mit Funktionszeigern kann schwierig sein. Das bedeutet nicht, dass normales C oder C++ genauso schwierig ist. Andere Sprachen lösen dies auf unterschiedliche Weise, einschließlich des Fehlens von Funktionszeigern, was in einigen Fällen eine erhebliche Unterlassung sein kann

    – Kate Gregory

    31. Dezember 2015 um 18:22 Uhr

  • Wenn Sie die Augen zusammenkneifen, sieht es aus wie LISP.

    – Benutzer2023861

    31. Dezember 2015 um 19:47 Uhr

Benutzeravatar von ouah
ouah

Es gibt eine Regel namens „Im Uhrzeigersinn/Spiralregel“ um die Bedeutung einer komplexen Deklaration zu finden.

Aus c-FAQ:

Es sind drei einfache Schritte zu befolgen:

  1. Beginnen Sie mit dem unbekannten Element und bewegen Sie sich spiralförmig/im Uhrzeigersinn; Wenn Sie den folgenden Elementen begegnen, ersetzen Sie sie durch die entsprechenden englischen Anweisungen:

    [X] oder []

    => Array X Größe von… oder Array undefinierte Größe von…

    (type1, type2)

    => Funktion, die Typ1 übergibt und Typ2 zurückgibt …

    *

    => Zeiger auf…

  2. Machen Sie dies weiter in einer Spiral-/Uhrzeigersinn-Richtung, bis alle Token abgedeckt sind.

  3. Lösen Sie immer zuerst alles in Klammern auf!

Beispiele finden Sie unter dem obigen Link.

Beachten Sie auch, dass es zu Ihrer Unterstützung auch eine Website mit dem Namen gibt:

http://www.cdecl.org

Sie können eine C-Deklaration eingeben, die ihre englische Bedeutung angibt. Zum

void (*(*f[])())()

es gibt aus:

Deklarieren Sie f als Array von Zeigern auf die Funktion, die den Zeiger auf die Funktion zurückgibt, die void zurückgibt

BEARBEITEN:

Wie in den Kommentaren von Random832 erwähnt, adressiert die Spiralregel kein Array von Arrays und führt in (den meisten) diesen Deklarationen zu einem falschen Ergebnis. Zum Beispiel für int **x[1][2]; Die Spiralregel ignoriert die Tatsache, dass [] hat Vorrang vor *.

Wenn man vor einem Array von Arrays steht, kann man zuerst explizite Klammern hinzufügen, bevor man die Spiralregel anwendet. Zum Beispiel: int **x[1][2]; ist das gleiche wie int **(x[1][2]); (auch gültiges C) aufgrund von Vorrang und die Spiralregel liest es dann korrekt als “x ist ein Array 1 von Array 2 von Zeiger auf Zeiger auf int”, was die korrekte englische Deklaration ist.

Beachten Sie, dass dieses Problem auch in dieser Antwort von James Kanze behandelt wurde (von Hacks in den Kommentaren darauf hingewiesen).

  • Ich wünschte, cdecl.org wäre besser

    – Grady-Spieler

    31. Dezember 2015 um 22:41 Uhr

  • Es gibt keine “Spiralregel”… “int ***foo[][][]” definiert ein Array von Arrays von Arrays von Zeigern auf Zeiger auf Zeiger. Die “Spirale” kommt nur von der Tatsache, dass diese Deklaration Dinge in Klammern so gruppiert, dass sie sich abwechseln. Es ist alles rechts, dann links , innerhalb jedes Satzes von Klammern.

    – Random832

    1. Januar 2016 um 1:43 Uhr


  • @Random832 Es gibt eine “Spiralregel”, die den gerade erwähnten Fall abdeckt, dh spricht darüber, wie mit Klammern / Arrays usw. umgegangen wird. Natürlich ist dies keine Standard-C-Regel, aber eine gute Eselsbrücke, um herauszufinden, wie man damit umgeht mit komplizierten Erklärungen. IMHO ist es äußerst nützlich und rettet Sie, wenn Sie in Schwierigkeiten sind oder wann cdecl.org kann die Deklaration nicht parsen. Natürlich sollte man solche Deklarationen nicht missbrauchen, aber es ist gut zu wissen, wie sie geparst werden.

    – vssoftco

    1. Januar 2016 um 1:55 Uhr


  • @vsoftco Aber es ist keine “Spirale / im Uhrzeigersinn bewegen”, wenn Sie sich nur umdrehen, wenn Sie die Klammern erreichen.

    – Random832

    1. Januar 2016 um 3:01 Uhr


  • ouah, Sie sollten erwähnen, dass die Spiralregel nicht universell ist.

    – Hacken

    1. Januar 2016 um 20:56 Uhr

Benutzeravatar von John Bode
Johannes Bode

Die “Spiral”-Regel fällt irgendwie aus den folgenden Vorrangregeln:

T *a[]    -- a is an array of pointer to T
T (*a)[]  -- a is a pointer to an array of T
T *f()    -- f is a function returning a pointer to T
T (*f)()  -- f is a pointer to a function returning T

Der Index [] und Funktionsaufruf () Operatoren haben eine höhere Priorität als unäre *Also *f() wird analysiert als *(f()) und *a[] wird analysiert als *(a[]).

Wenn Sie also einen Zeiger auf ein Array oder einen Zeiger auf eine Funktion wünschen, müssen Sie die explizit gruppieren * mit der Kennung, wie in (*a)[] oder (*f)().

Dann merkt man das a und f können kompliziertere Ausdrücke sein als nur Bezeichner; in T (*a)[N], a könnte ein einfacher Bezeichner sein, oder es könnte ein Funktionsaufruf sein (*f())[N] (a -> f()), oder es könnte ein Array wie sein (*p[M])[N](a -> p[M]), oder es könnte ein Array von Zeigern auf Funktionen wie sein (*(*p[M])())[N] (a -> (*p[M])()), etc.

Es wäre schön, wenn der Umleitungsoperator * war postfix statt unary, was die Lesbarkeit von Deklarationen von links nach rechts etwas erleichtern würde (void f[]*()*(); definitiv fließt besser als void (*(*f[])())()), aber es ist nicht.

Wenn Sie auf eine solche haarige Erklärung stoßen, beginnen Sie damit, die zu finden ganz links Bezeichner und wenden Sie die obigen Vorrangregeln an, indem Sie sie rekursiv auf alle Funktionsparameter anwenden:

         f              -- f
         f[]            -- is an array
        *f[]            -- of pointers  ([] has higher precedence than *)
       (*f[])()         -- to functions
      *(*f[])()         -- returning pointers
     (*(*f[])())()      -- to functions
void (*(*f[])())();     -- returning void

Das signal Funktion in der Standardbibliothek ist wohl das Musterbeispiel für diesen Wahnsinn:

       signal                                       -- signal
       signal(                          )           -- is a function with parameters
       signal(    sig,                  )           --    sig
       signal(int sig,                  )           --    which is an int and
       signal(int sig,        func      )           --    func
       signal(int sig,       *func      )           --    which is a pointer
       signal(int sig,      (*func)(int))           --    to a function taking an int                                           
       signal(int sig, void (*func)(int))           --    returning void
      *signal(int sig, void (*func)(int))           -- returning a pointer
     (*signal(int sig, void (*func)(int)))(int)     -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int);    -- and returning void

An dieser Stelle sagen die meisten Leute “typedefs verwenden”, was sicherlich eine Option ist:

typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);

innerfunc *f[N];

Aber…

Wie würdest du verwenden f in einem Ausdruck? Sie wissen, dass es sich um ein Array von Zeigern handelt, aber wie verwenden Sie es, um die richtige Funktion auszuführen? Sie müssen die Typedefs durchgehen und die richtige Syntax herausfinden. Im Gegensatz dazu ist die “nackte” Version ziemlich auffällig, aber sie sagt Ihnen genau, wie es geht verwenden f in einem Ausdruck (nämlich, (*(*f[i])())();unter der Annahme, dass keine der Funktionen Argumente benötigt).

  • Vielen Dank für das Beispiel „Signal“, das zeigt, dass solche Dinge in freier Wildbahn vorkommen.

    – Nur Salz

    31. Dezember 2015 um 19:02 Uhr

  • Das ist ein großartiges Beispiel.

    – Casey

    31. Dezember 2015 um 19:43 Uhr

  • Ich mochte deine f Verzögerungsbaum, Erklärung des Vorrangs … aus irgendeinem Grund bekomme ich immer einen Kick aus ASCII-Kunst, besonders wenn es darum geht, Dinge zu erklären 🙂

    – txtechhelp

    1. Januar 2016 um 21:00 Uhr

  • vorausgesetzt, dass keine der Funktionen Argumente benötigt: dann müssen Sie verwenden void in Funktionsklammern, ansonsten kann es beliebige Argumente annehmen.

    – Hacken

    2. Januar 2016 um 11:22 Uhr

  • @hackks: für die Deklaration, ja; Ich sprach über den Funktionsaufruf.

    – Johannes Bode

    2. Januar 2016 um 14:46 Uhr

In C spiegelt die Deklaration die Verwendung wider – so ist sie im Standard definiert. Die Erklärung:

void (*(*f[])())()

Ist eine Behauptung, dass der Ausdruck (*(*f[i])())() erzeugt ein Ergebnis vom Typ void. Was bedeutet:

  • f muss ein Array sein, da Sie es indizieren können:

    f[i]
    
  • Die Elemente von f müssen Zeiger sein, da Sie sie dereferenzieren können:

    *f[i]
    
  • Diese Zeiger müssen Zeiger auf Funktionen sein, die keine Argumente annehmen, da Sie sie aufrufen können:

    (*f[i])()
    
  • Die Ergebnisse dieser Funktionen müssen ebenfalls Zeiger sein, da Sie sie dereferenzieren können:

    *(*f[i])()
    
  • Diese Hinweise müssen Auch Zeiger auf Funktionen sein, die keine Argumente annehmen, da Sie sie aufrufen können:

    (*(*f[i])())()
    
  • Diese Funktionszeiger müssen zurückkehren void

Die „Spiralregel“ ist nur eine Eselsbrücke, die eine andere Art bietet, dieselbe Sache zu verstehen.

  • Eine tolle Betrachtungsweise, die ich so noch nie gesehen habe. +1

    – bodt

    2. Januar 2016 um 20:53 Uhr

  • Nett. So gesehen, es ist wirklich einfach. Eigentlich eher einfacher als sowas vector< function<function<void()>()>* > fbesonders wenn Sie die hinzufügen std::s. (Aber gut, das Beispiel ist erfunden … sogar f :: [IORef (IO (IO ()))] sieht komisch aus.)

    – linksherum

    2. Januar 2016 um 23:50 Uhr


  • @TimoDenk: Die Deklaration a[x] gibt an, dass der Ausdruck a[i] gilt wann i >= 0 && i < x. Wohingegen, a[] lässt die Größe unbestimmt und ist daher identisch mit *a: es zeigt an, dass der Ausdruck a[i] (oder gleichwertig *(a + i)) gilt für etwas Bereich von i.

    – Jon Purdy

    5. Januar 2016 um 21:37 Uhr

  • Dies ist bei weitem die einfachste Art, über C-Typen nachzudenken, danke dafür

    – Alex Özer

    5. Januar 2016 um 22:22 Uhr

  • Ich liebe es! Viel einfacher zu argumentieren als dumme Spiralen. (*f[])() ist ein Typ, den Sie indizieren, dann dereferenzieren und dann aufrufen können, also ist es ein Array von Zeigern auf Funktionen.

    – Lynne

    16. Januar 2016 um 0:46 Uhr

Benutzeravatar von hackks
hackt

Also ist dieses “Spirallesen” etwas Gültiges?

Spiralregel anwenden oder verwenden cdekl sind nicht immer gültig. Beides versagt in manchen Fällen. Die Spiralregel funktioniert in vielen Fällen, ist aber nicht universell.

Um komplexe Deklarationen zu entschlüsseln, erinnern Sie sich an diese zwei einfachen Regeln:

  • Lesen Sie Erklärungen immer von innen nach außen: Beginnen Sie mit der innersten Klammer, falls vorhanden. Suchen Sie den Bezeichner, der deklariert wird, und beginnen Sie von dort aus, die Deklaration zu entschlüsseln.

  • Wenn du die Wahl hast, bevorzuge immer [] und () Über *: Wenn * steht vor dem Bezeichner und [] folgt, stellt der Bezeichner ein Array dar, keinen Zeiger. Ebenso wenn * steht vor dem Bezeichner und () folgt, repräsentiert der Bezeichner eine Funktion, keinen Zeiger. (Klammern können immer verwendet werden, um die normale Priorität von zu überschreiben [] und () Über *.)

Diese Regel beinhaltet tatsächlich im Zickzack von einer Seite der Kennung zur anderen.

Entschlüsseln Sie jetzt eine einfache Erklärung

int *a[10];

Regel anwenden:

int *a[10];      "a is"  
     ^  

int *a[10];      "a is an array"  
      ^^^^ 

int *a[10];      "a is an array of pointers"
    ^

int *a[10];      "a is an array of pointers to `int`".  
^^^      

Lassen Sie uns die komplexe Deklaration wie entschlüsseln

void ( *(*f[]) () ) ();  

indem Sie die obigen Regeln anwenden:

void ( *(*f[]) () ) ();        "f is"  
          ^  

void ( *(*f[]) () ) ();        "f is an array"  
           ^^ 

void ( *(*f[]) () ) ();        "f is an array of pointers" 
         ^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function"   
               ^^     

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer"
       ^   

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function" 
                    ^^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function returning `void`"  
^^^^

Hier ist ein GIF, das zeigt, wie Sie vorgehen (zum Vergrößern auf das Bild klicken):

Geben Sie hier die Bildbeschreibung ein


Die hier erwähnten Regeln stammen aus dem Buch C Programming A Modern Approach von KN KING.

Es ist nur eine “Spirale”, weil es in dieser Deklaration zufällig nur einen Operator auf jeder Seite innerhalb jeder Ebene von Klammern gibt. Die Behauptung, dass Sie “in einer Spirale” vorgehen, würde im Allgemeinen darauf hindeuten, dass Sie in der Deklaration zwischen Arrays und Zeigern wechseln int ***foo[][][] wenn in Wirklichkeit alle Array-Ebenen vor allen Pointer-Ebenen stehen.

  • Nun, beim “Spiralansatz” geht man so weit rechts wie möglich, dann so weit links wie möglich usw. Aber es wird oft fälschlicherweise erklärt …

    – Lynne

    16. Januar 2016 um 0:48 Uhr

Benutzeravatar von SergeyA
SergejA

Ich bezweifle, dass Konstruktionen wie diese im wirklichen Leben von Nutzen sein können. Ich verabscheue sie sogar als Interviewfragen für die normalen Entwickler (wahrscheinlich OK für Compiler-Autoren). Stattdessen sollten typedefs verwendet werden.

  • Nun, beim “Spiralansatz” geht man so weit rechts wie möglich, dann so weit links wie möglich usw. Aber es wird oft fälschlicherweise erklärt …

    – Lynne

    16. Januar 2016 um 0:48 Uhr

Benutzeravatar von asamarin
asamarin

Als zufälliges Trivia-Faktoid finden Sie es vielleicht amüsant zu wissen, dass es im Englischen ein tatsächliches Wort gibt, das beschreibt, wie C-Deklarationen gelesen werden: Boustrophedonischalso abwechselnd von rechts nach links und von links nach rechts.

Bezug: Van der Linden, 1994Seite 76

  • Dieses Wort zeigt nicht an innerhalb wie in durch Klammern verschachtelt oder in einer einzelnen Zeile. Es beschreibt ein “Schlangen”-Muster mit einer LTR-Linie gefolgt von einer RTL-Linie.

    – Kartoffelklatsche

    3. Januar 2016 um 5:29 Uhr

1425620cookie-checkC ist nicht so schwer: void ( *( *f[] ) () ) ()

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy