Ich habe kürzlich die Spiralregel zum Entschlüsseln komplexer Deklarationen gelernt, die mit einer Serie geschrieben worden sein müssen von typedefs. Der folgende Kommentar alarmiert mich jedoch:
Eine häufig zitierte Vereinfachung, die nur für wenige funktioniert einfache Fälle.
Ich finde void (*signal(int, void (*fp)(int)))(int);
nicht als "einfachen Fall". Was um so beunruhigender ist.
Also, meine Frage ist, in welchen Situationen werde ich die Regel richtig anwenden, und in welcher wäre es falsch?
Grundsätzlich funktioniert die Regel einfach nicht oder anders funktioniert, indem es neu definiert, was mit Spirale gemeint ist (in diesem Fall Es hat keinen Sinn. Betrachten Sie zum Beispiel:
%Vor% Die spiralige Regel würde geben a ist ein Array [10] des Zeigers zu
Array [15] von int, was falsch ist. Es ist der Fall, dass Sie es stellen
funktioniert auch nicht; im Fall von signal
ist es das nicht
sogar klar, wo Sie die Spirale beginnen sollten.
Im Allgemeinen ist es einfacher, Beispiele dafür zu finden, wo die Regel fehlschlägt als Beispiele, wo es funktioniert.
Ich bin oft versucht zu sagen, dass das Parsen einer C ++ - Deklaration ist einfach, aber kein Körper, der mit komplizierten Erklärungen versucht hat würde mir glauben. Auf der anderen Seite ist es nicht so schwer wie es ist manchmal gemacht zu sein. Das Geheimnis ist, an das zu denken Erklärung genau wie Sie einen Ausdruck, aber mit einer Menge weniger Operatoren und eine sehr einfache Vorrangregel: alle Operatoren auf der rechten Seite haben Vorrang vor allen Betreibern auf der linken Seite. Im Das Fehlen von Klammern bedeutet, dass alles zu den zuerst rechts, dann alles nach links, und verarbeiten Klammern genau wie in jedem anderen Ausdruck. Das tatsächliche Schwierigkeit ist nicht die Syntax an sich, sondern dass es Ergebnisse sind einige sehr komplexe und kontraintuitive Erklärungen, insbesondere wo Funktion Rückgabewerte und Zeiger auf Funktionen sind beteiligt: die erste rechts, dann linke Regel bedeutet dass die Betreiber auf einer bestimmten Ebene oft weit voneinander entfernt sind, z.B.:
%Vor% Der letzte Begriff in der Erweiterung ist int[10]
, aber Putting
Die [10]
nach der vollständigen Funktionsspezifikation ist (at
zumindest für mich) sehr unnatürlich, und ich muss aufhören und es erarbeiten
jedes Mal. (Es ist wahrscheinlich diese Tendenz logisch benachbart
sich ausbreitende Teile, die zur Spiralregel führen. Das Problem
ist natürlich, dass in Abwesenheit von Klammern sie nicht
immer verteilt - immer wenn du [i][j]
siehst, ist die Regel weg
rechts, dann geh wieder nach rechts, anstatt Spirale.)
Und da wir jetzt an Erklärungen in Bezug auf denken
Ausdrücke: Was machst du, wenn ein Ausdruck auch wird
kompliziert zu lesen? Sie führen Zwischenvariablen in der Reihenfolge ein
um es leichter zu lesen. Im Falle von Erklärungen, die
"Zwischenvariablen" sind typedef
. Insbesondere würde ich
argumentieren, dass jederzeit ein Teil des Rückgabetyps nach dem endet
Funktion Argumente (und viele andere Zeiten auch), Sie
sollte ein typedef
verwenden, um die Deklaration zu vereinfachen. (Dies
ist jedoch eine "tu wie ich sage, nicht wie ich tue". Ich befürchte, dass
Ich werde gelegentlich einige sehr komplexe Deklarationen verwenden.)
Die Regel ist richtig. Allerdings sollte man bei der Anwendung sehr vorsichtig sein.
Ich schlage vor, es formeller für C99 + Deklarationen anzuwenden.
Am wichtigsten ist hier die folgende rekursive Struktur aller Deklarationen ( const
, volatile
, static
, extern
, inline
, struct
, union
, typedef
werden der Einfachheit halber aus dem Bild entfernt, können aber einfach wieder hinzugefügt werden):
Ja, das ist es, vier Teile.
%Vor%Sobald Sie alle 4 Teile analysiert haben,
[ object
] ist [ derived-part2
(enthält / gibt zurück)] [ derived-part2
(Zeiger auf)] base-type
1 .
Wenn es eine Rekursion gibt, findest du deine object
(falls es welche gibt) unten im Rekursionsstack, es ist die innerste und du bekommst die vollständige Deklaration, indem du zurückkommst und sammelst Kombinieren von abgeleiteten Teilen auf jeder Rekursionsebene.
Beim Parsen können Sie [object]
nach after [derived-part2]
(falls vorhanden) verschieben. Dies wird Ihnen eine linearisierte, leicht verständliche Erklärung geben (siehe 1 oben).
Also, in
%Vor%Sie erhalten:
base-type
= char
derived-part1
= *
, object
= (**(*foo[3][5])(void))
, derived-part2
= [7][9]
derived-part1
= **
, object
= (*foo[3][5])
, derived-part2
= (void)
derived-part1
= *
, object
= foo
, derived-part2
= [3][5]
Von dort:
*
[3][5]
foo
**
(void)
*
[3][5]
foo
*
[7][9]
**
(void)
*
[3][5]
foo
char
*
[7][9]
**
(void)
*
[3][5]
foo
Lesen Sie nun von rechts nach links:
foo
ist ein Array von 3 Arrays mit 5 Zeigern zu einer Funktion (keine Parameter), die einen Zeiger auf einen Zeiger auf ein Array von 7 Arrays mit 9 Zeigern auf ein Zeichen zurückgibt.
Sie könnten die Array-Dimensionen auch in jedem derived-part2
in diesem Prozess umkehren.
Das ist deine Spiralregel.
Und es ist leicht, die Spirale zu sehen. Man taucht in das immer tiefer verschachtelte [object]
von links ein und taucht dann wieder rechts auf, nur um zu bemerken, dass es auf der oberen Ebene ein weiteres Paar von links und rechts usw. gibt.
ZB:
%Vor% Dies ist kein Array von Zeigern auf Arrays von int
.
Tags und Links c declaration