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


188

Ich habe heute gerade ein Bild gesehen und denke, ich würde mich über Erklärungen freuen. Also hier ist das Bild:

etwas c-Code

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

eine interessante Erklärung

Also ist dieses "spiralförmige Lesen" etwas Gültiges? Analysieren C-Compiler auf diese Weise?
Es wäre großartig, wenn es einfachere Erklärungen für diesen seltsamen Code gäbe.
Kann diese Art von Codes, abgesehen von allem, 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 die Verwendung solcher Ausdrücke und die Gültigkeit der Spiralregel. In Bezug auf diese sind einige nette Antworten bereits veröffentlicht.


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

29
Vielleicht ist C schwer? Das deklariert tatsächlich fals ein Array von Zeigern auf Funktionen, die jedes Argument annehmen könnten. Wenn es so wäre void (*(*f[])(void))(void);, dann wären es Funktionen, die keine Argumente
annehmen

18
In der Praxis codieren Sie keinen solchen obskuren Code. Verwenden Sie typedef für Unterschriften
Basile Starynkevitch

4
Jede Deklaration mit Funktionszeigern kann schwierig sein. Das bedeutet nicht, dass normales C oder C ++ auf die gleiche Weise schwierig ist. Andere Sprachen lösen dies auf unterschiedliche Weise, einschließlich fehlender Funktionszeiger, was in einigen Fällen eine erhebliche Auslassung sein kann
Kate Gregory

20
Wenn Sie die Augen zusammenknicken, sieht es aus wie LISP.
user2023861

Antworten:


117

Es gibt eine Regel namens "Clockwise / Spiral Rule" , um die Bedeutung einer komplexen Deklaration zu ermitteln.

Aus c-faq :

Es gibt drei einfache Schritte:

  1. Beginnen Sie mit dem unbekannten Element und bewegen Sie sich spiralförmig / im Uhrzeigersinn. Wenn Sie auf folgende Elemente stoßen, ersetzen Sie diese durch die entsprechenden englischen Aussagen:

    [X]oder []
    => Array X Größe von ... oder Array undefinierte Größe von ...

    (type1, type2)
    => Funktion übergibt Typ1 und Typ2 und gibt ...

    *
    => Zeiger auf ...

  2. Machen Sie dies spiralförmig / im Uhrzeigersinn, bis alle Token bedeckt sind.

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

Sie können den obigen Link für Beispiele überprüfen.

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

http://www.cdecl.org

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

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

es gibt aus:

deklarieren Sie f als Array des Zeigers auf die Funktion, die den Zeiger auf die Funktion zurückgibt, die void zurückgibt

BEARBEITEN:

Wie in den Kommentaren von Random832 ausgeführt , behandelt die Spiralregel kein Array von Arrays und führt bei (den meisten) dieser Deklarationen zu einem falschen Ergebnis. Zum Beispiel int **x[1][2];ignoriert die Spiralregel die Tatsache, dass[] einen höheren Vorrang hat *.

Wenn Sie sich vor einem Array von Arrays befinden, können Sie zunächst explizite Klammern hinzufügen, bevor Sie die Spiralregel anwenden. Beispielsweise:int **x[1][2]; ist int **(x[1][2]);aufgrund der Priorität dasselbe wie (auch gültiges C) 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 (auf Haccks in den Kommentaren hingewiesen ).


5
Ich wünschte, cdecl.org wäre besser
Grady Player

8
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 Erklärung dazu führte, dass Dinge in Klammern so gruppiert wurden, dass sie sich abwechselten. Es ist alles rechts und dann links in jedem Satz von Klammern.
Random832

1
@ Random832 Es gibt eine "Spiralregel", die den gerade erwähnten Fall abdeckt, dh über den Umgang mit Klammern / Arrays usw. spricht. Natürlich handelt es sich nicht um eine Standard-C-Regel, sondern um eine gute Mnemonik, um herauszufinden, wie man damit umgeht mit komplizierten Erklärungen. IMHO, es ist äußerst nützlich und erspart Ihnen, wenn Sie in Schwierigkeiten sind oder wenn cdecl.org die Deklaration nicht analysieren kann. Natürlich sollte man solche Erklärungen nicht missbrauchen, aber es ist gut zu wissen, wie sie analysiert werden.
vsoftco

5
@vsoftco Aber es bewegt sich nicht "spiralförmig / im Uhrzeigersinn", wenn Sie sich nur umdrehen, wenn Sie die Klammern erreichen.
Random832

2
ouah, du solltest erwähnen, dass die Spiralregel nicht universell ist .
haccks

105

Die "Spiral" -Regel fällt aus den folgenden Vorrangregeln heraus:

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

Die Operatoren für Index- []und Funktionsaufrufe ()haben eine höhere Priorität als unary *und werden daher *f()als *(f())und *a[]als analysiert *(a[]).

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

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

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

Wenn Sie auf eine solche haarige Deklaration stoßen, suchen Sie zunächst den Bezeichner ganz links und wenden Sie die oben genannten Prioritätsregeln 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

Die signalFunktion in der Standardbibliothek ist wahrscheinlich das Muster für diese Art von 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 diesem Punkt sagen die meisten Leute "benutze typedefs", was sicherlich eine Option ist:

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

innerfunc *f[N];

Aber...

Wie würden Sie 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 augenfällig, aber sie sagt Ihnen genau, wie man sie f in einem Ausdruck verwendet ( (*(*f[i])())();vorausgesetzt, keine der beiden Funktionen benötigt Argumente).


7
Vielen Dank, dass Sie das Beispiel "Signal" gegeben haben, das zeigt, dass solche Dinge in freier Wildbahn auftreten.
Justsalt

Das ist ein gutes Beispiel.
Casey

Ich mochte Ihren fVerzögerungsbaum, der den Vorrang erklärt ... aus irgendeinem Grund bekomme ich immer einen Kick von ASCII-Kunst, besonders wenn es darum geht, Dinge zu erklären :)
txtechhelp

1
Angenommen, keine der beiden Funktionen akzeptiert Argumente : Dann müssen Sie sie voidin Klammern für Funktionen verwenden, andernfalls können beliebige Argumente verwendet werden.
haccks

1
@haccks: für die Erklärung ja; Ich habe über den Funktionsaufruf gesprochen.
John Bode

57

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

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

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

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

    f[i]
  • Die Elemente von fmü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 auch Zeiger sein, da Sie sie dereferenzieren können:

    *(*f[i])()
  • Diese Zeiger 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 Mnemonik, die eine andere Art bietet, dasselbe zu verstehen.


3
Tolle Sichtweise, die ich noch nie gesehen habe. +1
tbodt

4
Nett. So gesehen ist es wirklich einfach . Eigentlich eher einfacher als so etwas vector< function<function<void()>()>* > f, vor allem wenn man das std::s hinzufügt . (Aber gut, das Beispiel ist erfunden ... f :: [IORef (IO (IO ()))]sieht sogar komisch aus.)
Links um den

1
@ TimoDenk: Die Deklaration a[x]gibt an, dass der Ausdruck a[i]gültig ist, wenn i >= 0 && i < x. Lässt a[]die Größe nicht spezifiziert und ist daher identisch mit *a: Es zeigt an, dass der Ausdruck a[i](oder gleichwertig *(a + i)) für einen bestimmten Bereich von gültig ist i.
Jon Purdy

4
Dies ist bei weitem der einfachste Weg, um über C-Typen nachzudenken, danke dafür
Alex Ozer

4
Ich liebe es! Viel einfacher zu überlegen als alberne Spiralen. (*f[])()ist ein Typ, den Sie indizieren, dann dereferenzieren und dann aufrufen können. Es handelt sich also um ein Array von Zeigern auf Funktionen.
Lynn

32

Also ist dieses "spiralförmige Lesen" etwas Gültiges?

Das Anwenden der Spiralregel oder das Verwenden von cdecl sind nicht immer gültig. Beides schlägt in einigen Fällen fehl. Die Spiralregel funktioniert in vielen Fällen, ist aber nicht universell .

Um komplexe Deklarationen zu entschlüsseln, beachten Sie diese beiden einfachen Regeln:

  • Lesen Sie die Erklärungen immer von innen nach außen : Beginnen Sie gegebenenfalls in der innersten Klammer. Suchen Sie die Kennung, die deklariert wird, und entschlüsseln Sie die Deklaration von dort aus.

  • Wenn es eine Auswahl gibt, bevorzugen Sie immer []und ()mehr* : Wenn *vor dem Bezeichner steht und ihm []folgt, repräsentiert der Bezeichner ein Array, keinen Zeiger. Wenn *der Bezeichner vor und ()nach ihm steht, repräsentiert der Bezeichner eine Funktion und keinen Zeiger. (Klammern können immer verwendet werden, um die normale Priorität von []und ()über zu überschreiben *.)

Diese Regel beinhaltet tatsächlich das Zick- Zack- Verfahren von einer Seite des Bezeichners zur anderen.

Entschlüsseln Sie nun 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[]) () ) ();  

durch Anwendung der oben genannten Regeln:

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 (klicken Sie auf das Bild, um es zu vergrößern):

Geben Sie hier die Bildbeschreibung ein


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


Dies ist genau wie der Ansatz des Standards, dh "Deklaration spiegelt die Verwendung wider". Ich möchte an dieser Stelle jedoch noch etwas anderes fragen: Schlagen Sie das Buch von KN King vor? Ich sehe viele nette Kritiken über das Buch.
Motun

1
Ja. Ich schlage dieses Buch vor. Ich habe angefangen, aus diesem Buch heraus zu programmieren. Gute Texte und Probleme drin.
haccks

Können Sie ein Beispiel dafür geben, dass cdecl eine Deklaration nicht versteht? Ich dachte, cdecl verwendet dieselben Parsing-Regeln wie Compiler, und soweit ich das beurteilen kann, funktioniert es immer.
Fabio sagt Reinstate Monica

@ FabioTurati; Eine Funktion kann keine Arrays oder Funktionen zurückgeben. char (x())[5]sollte zu einem Syntaxfehler führen, aber cdecl parse es als: deklariere xals Funktion, die Array 5 von zurückgibtchar .
haccks

12

Es ist nur eine "Spirale", weil es in dieser Deklaration nur einen Operator auf jeder Seite in jeder Ebene von Klammern gibt. Die Behauptung, dass Sie "spiralförmig" vorgehen, würde im Allgemeinen bedeuten, dass Sie in der Deklaration zwischen Arrays und Zeigern wechseln, int ***foo[][][]wenn in Wirklichkeit alle Array-Ebenen vor einer der Zeiger-Ebenen liegen.


Nun, im "Spiralansatz" gehen Sie so weit wie möglich nach rechts, dann so weit wie möglich nach links usw. Aber es wird oft fälschlicherweise erklärt ...
Lynn

7

Ich bezweifle, dass solche Konstruktionen im wirklichen Leben von Nutzen sein können. Ich verabscheue sie sogar als Interviewfragen für die regulären Entwickler (wahrscheinlich in Ordnung für Compiler-Autoren). Stattdessen sollten typedefs verwendet werden.


3
Trotzdem ist es wichtig zu wissen, wie man es analysiert, auch wenn man nur weiß, wie man das typedef analysiert!
Inetknght

1
@inetknght, die Art und Weise, wie Sie es mit typedefs tun, besteht darin, sie so einfach zu halten, dass kein Parsen erforderlich wäre.
SergeyA

2
Menschen, die diese Art von Fragen während eines Interviews stellen, tun dies nur, um ihre Egos zu streicheln.
Casey

1
@JohnBode, und Sie würden sich selbst einen Gefallen tun, indem Sie den Rückgabewert der Funktion eingeben.
SergeyA

1
@ JohnBode, ich finde es eine Frage der persönlichen Wahl, die es nicht wert ist, diskutiert zu werden. Ich sehe deine Präferenz, ich habe immer noch meine.
SergeyA

7

Als zufälliges Trivia-Faktoid ist es vielleicht amüsant zu wissen, dass es auf Englisch ein tatsächliches Wort gibt, das beschreibt, wie C-Deklarationen gelesen werden: Boustrophedonisch , dh abwechselnd von rechts nach links mit von links nach rechts.

Referenz: Van der Linden, 1994 - Seite 76


1
Dieses Wort zeigt nicht innerhalb von verschachtelt durch Parens oder in einer einzelnen Zeile an. Es beschreibt ein "Schlangen" -Muster mit einer LTR-Linie, gefolgt von einer RTL-Linie.
Potatoswatter

5

In Bezug auf die Nützlichkeit davon sehen Sie bei der Arbeit mit Shellcode viel von diesem Konstrukt:

int (*ret)() = (int(*)())code;
ret();

Obwohl nicht ganz so syntaktisch kompliziert, kommt dieses spezielle Muster häufig vor.

Vollständigeres Beispiel in dieser SO-Frage.

Während die Nützlichkeit im Ausmaß des Originalbilds fraglich ist (ich würde vorschlagen, dass jeder Produktionscode drastisch vereinfacht werden sollte), gibt es einige syntaktische Konstrukte, die ziemlich häufig auftauchen.


5

Die Erklärung

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

ist nur eine obskure Art zu sagen

Function f[]

mit

typedef void (*ResultFunction)();

typedef ResultFunction (*Function)();

In der Praxis werden anstelle von ResultFunction und Function aussagekräftigere Namen benötigt . Wenn möglich würde ich auch die Parameterlisten als angeben void.


4

Ich fand die von Bruce Eckel beschriebene Methode hilfreich und leicht zu befolgen:

Funktionszeiger definieren

Um einen Zeiger auf eine Funktion zu definieren, die keine Argumente und keinen Rückgabewert enthält, sagen Sie:

void (*funcPtr)();

Wenn Sie sich eine komplexe Definition wie diese ansehen, ist der beste Weg, sie anzugreifen, in der Mitte zu beginnen und sich herauszuarbeiten. "In der Mitte beginnen" bedeutet, mit dem Variablennamen funcPtr zu beginnen. „Ausarbeiten“ bedeutet, nach rechts nach dem nächsten Element zu suchen (in diesem Fall nichts; die rechte Klammer hält Sie kurz), dann nach links zu schauen (ein durch das Sternchen gekennzeichneter Zeiger) und dann nach rechts zu schauen (an leere Argumentliste, die eine Funktion angibt, die keine Argumente akzeptiert), dann nach links schauen (void, was anzeigt, dass die Funktion keinen Rückgabewert hat). Diese Bewegung von rechts nach links nach rechts funktioniert mit den meisten Deklarationen.

Um zu überprüfen, "beginne in der Mitte" ("funcPtr ist ein ..."), gehe nach rechts (nichts dort - du wirst durch die rechte Klammer gestoppt), gehe nach links und finde das '*' (" ... Zeiger auf ein ... ”), gehe nach rechts und finde die leere Argumentliste (“ ... Funktion, die keine Argumente akzeptiert ... ”), gehe nach links und finde die Leere (“ funcPtr is ein Zeiger auf eine Funktion, die keine Argumente akzeptiert und void zurückgibt ”).

Sie fragen sich vielleicht, warum * funcPtr Klammern erfordert. Wenn Sie sie nicht verwenden würden, würde der Compiler Folgendes sehen:

void *funcPtr();

Sie würden eine Funktion deklarieren (die eine Leere * zurückgibt), anstatt eine Variable zu definieren. Sie können sich vorstellen, dass der Compiler denselben Prozess durchläuft, den Sie ausführen, wenn er herausfindet, was eine Deklaration oder Definition sein soll. Diese Klammern müssen „gegen“ stoßen, damit sie nach links zurückkehren und das '*' finden, anstatt nach rechts fortzufahren und die leere Argumentliste zu finden.

Komplizierte Erklärungen und Definitionen

Abgesehen davon können Sie, sobald Sie herausgefunden haben, wie die C- und C ++ - Deklarationssyntax funktioniert, viel kompliziertere Elemente erstellen. Zum Beispiel:

//: C03:ComplicatedDefinitions.cpp

/* 1. */     void * (*(*fp1)(int))[10];

/* 2. */     float (*(*fp2)(int,int,float))(int);

/* 3. */     typedef double (*(*(*fp3)())[10])();
             fp3 a;

/* 4. */     int (*(*f4())[10])();


int main() {} ///:~ 

Gehen Sie durch jeden einzelnen und verwenden Sie die Rechts-Links-Richtlinie, um dies herauszufinden. Nummer 1 besagt, dass "fp1 ein Zeiger auf eine Funktion ist, die ein ganzzahliges Argument verwendet und einen Zeiger auf ein Array von 10 ungültigen Zeigern zurückgibt."

Nummer 2 besagt: "fp2 ist ein Zeiger auf eine Funktion, die drei Argumente (int, int und float) akzeptiert und einen Zeiger auf eine Funktion zurückgibt, die ein ganzzahliges Argument verwendet und ein float zurückgibt."

Wenn Sie viele komplizierte Definitionen erstellen, möchten Sie möglicherweise ein typedef verwenden. Nummer 3 zeigt, wie ein typedef das Eingeben der komplizierten Beschreibung jedes Mal speichert. Es heißt: "Ein fp3 ist ein Zeiger auf eine Funktion, die keine Argumente akzeptiert und einen Zeiger auf ein Array von 10 Zeigern auf Funktionen zurückgibt, die keine Argumente annehmen und Doppelte zurückgeben." Dann heißt es: "a ist einer dieser fp3-Typen." typedef ist im Allgemeinen nützlich, um komplizierte Beschreibungen aus einfachen zu erstellen.

Nummer 4 ist eine Funktionsdeklaration anstelle einer Variablendefinition. Es heißt: "f4 ist eine Funktion, die einen Zeiger auf ein Array von 10 Zeigern auf Funktionen zurückgibt, die ganze Zahlen zurückgeben."

Sie werden selten oder nie so komplizierte Erklärungen und Definitionen wie diese benötigen. Wenn Sie jedoch die Übung durchlaufen, sie herauszufinden, werden Sie nicht einmal leicht durch die etwas komplizierten gestört, denen Sie im wirklichen Leben begegnen können.

Entnommen aus: Thinking in C ++, Band 1, zweite Ausgabe, Kapitel 3, Abschnitt "Funktionsadressen" von Bruce Eckel.


4

Denken Sie an diese Regeln für C-Deklarationen
. Die Priorität wird niemals in Zweifel gezogen:
Beginnen Sie mit dem Suffix, fahren Sie mit dem Präfix fort
und lesen Sie beide Sätze von innen nach außen.
- Ich, Mitte der 1980er Jahre

Außer natürlich in Klammern geändert. Beachten Sie, dass die Syntax zum Deklarieren dieser Werte genau die Syntax zum Verwenden dieser Variablen zum Abrufen einer Instanz der Basisklasse widerspiegelt.

Im Ernst, das ist nicht schwer auf einen Blick zu lernen. Sie müssen nur bereit sein, einige Zeit damit zu verbringen, die Fertigkeit zu üben. Wenn Sie C-Code pflegen oder anpassen möchten, der von anderen Personen geschrieben wurde, lohnt es sich auf jeden Fall , diese Zeit zu investieren. Es ist auch ein lustiger Partytrick, um andere Programmierer auszuflippen, die es nicht gelernt haben.

Für Ihren eigenen Code: wie immer, die Tatsache , dass etwas kann als does't Einzeiler geschrieben werden bedeutet es sein sollte, es sei denn , es ein extrem übliches Muster ist , die ein Standard Idiom (wie die String-copy - Schleife) worden sind . Sie und diejenigen, die Ihnen folgen, werden viel glücklicher sein, wenn Sie komplexe Typen aus geschichteten Typedefs und schrittweisen Dereferenzen erstellen, anstatt sich auf Ihre Fähigkeit zu verlassen, diese "auf einen Schlag" zu generieren und zu analysieren. Die Leistung wird genauso gut sein, und die Lesbarkeit und Wartbarkeit des Codes wird erheblich besser sein.

Es könnte schlimmer sein, weißt du? Es gab eine rechtliche PL / I-Erklärung, die mit etwas begann wie:

if if if = then then then = else else else = if then ...

2
Die PL / I-Anweisung wurde IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIFund wird analysiert als if (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF).
Cole Johnson

Ich denke, es gab eine Version, die einen Schritt weiter ging, indem sie einen bedingten IF / THEN / ELSE-Ausdruck (äquivalent zu C? :) verwendete, der den dritten Satz in die Mischung brachte ... aber es ist ein paar Jahrzehnte her und hat es vielleicht getan hing von einem bestimmten Dialekt der Sprache ab. Es bleibt der Punkt, dass jede Sprache mindestens eine pathologische Form hat.
Keshlam

4

Ich bin zufällig der ursprüngliche Autor der Spiralregel, die ich vor so vielen Jahren geschrieben habe (als ich viele Haare hatte :) und wurde geehrt, als sie dem cfaq hinzugefügt wurde.

Ich habe die Spiralregel geschrieben, um meinen Schülern und Kollegen das Lesen der C-Erklärungen "in ihrem Kopf" zu erleichtern. Das heißt, ohne Software-Tools wie cdecl.org usw. verwenden zu müssen. Es war nie meine Absicht zu erklären, dass die Spiralregel die kanonische Methode zum Parsen von C-Ausdrücken ist. Ich freue mich jedoch zu sehen, dass die Regel im Laufe der Jahre buchstäblich Tausenden von Studenten und Praktikern der C-Programmierung geholfen hat!

Für die Aufzeichnung,

Es wurde mehrfach "richtig" identifiziert, unter anderem von Linus Torvalds (jemand, den ich sehr respektiere), dass es Situationen gibt, in denen meine Spiralregel "zusammenbricht". Das häufigste Wesen:

char *ar[10][10];

Wie von anderen in diesem Thread hervorgehoben, könnte die Regel aktualisiert werden, um zu sagen, dass bei der Begegnung mit Arrays einfach alle Indizes so verwendet werden, als ob sie wie folgt geschrieben wären :

char *(ar[10][10]);

Nach der Spiralregel würde ich nun Folgendes erhalten:

"ar ist ein zweidimensionales 10x10-Array von Zeigern auf char"

Ich hoffe, dass die Spiralregel ihre Nützlichkeit beim Lernen von C fortsetzt!

PS:

Ich liebe das Bild "C ist nicht schwer" :)


3
  • Leere (*(*f[]) ()) ()

Auflösen void>>

  • (*(*f[]) ()) () = nichtig

Resoiving ()>>

  • (* (*f[]) ()) = Rückgabe der Funktion (ungültig)

Auflösen *>>

  • (*f[]) () = Zeiger auf (Rückgabe der Funktion (void))

Auflösen ()>>

  • (* f[]) = Funktionsrückgabe (Zeiger auf (Funktionsrückgabe (void)))

Auflösen *>>

  • f[] = Zeiger auf (Funktionsrückgabe (Zeiger auf (Funktionsrückgabe (void))))

Auflösen [ ]>>

  • f = Array von (Zeiger auf (Funktionsrückgabe (Zeiger auf (Funktionsrückgabe (void))))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.