Die Spiralregel über Deklarationen - wann ist sie falsch?

8

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?

    
Vorac 28.04.2013, 06:52
quelle

3 Antworten

16

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.)

    
James Kanze 28.04.2013 17:04
quelle
3

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):

%Vor%

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:

  1. base-type = char
  2. Ebene 1: derived-part1 = * , object = (**(*foo[3][5])(void)) , derived-part2 = [7][9]
  3. Ebene 2: derived-part1 = ** , object = (*foo[3][5]) , derived-part2 = (void)
  4. Ebene 3: derived-part1 = * , object = foo , derived-part2 = [3][5]

Von dort:

  1. Stufe 3: * [3][5] foo
  2. Ebene 2: ** (void) * [3][5] foo
  3. Ebene 1: * [7][9] ** (void) * [3][5] foo
  4. schließlich, 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.

    
Alexey Frunze 28.04.2013 09:55
quelle
2

ZB:

%Vor%

Dies ist kein Array von Zeigern auf Arrays von int .

    
Charles Bailey 28.04.2013 08:23
quelle

Tags und Links