Warum funktioniert a +++++ b nicht?

88
int main ()
{
   int a = 5,b = 2;
   printf("%d",a+++++b);
   return 0;
}

Dieser Code gibt den folgenden Fehler aus:

Fehler: lWert als Inkrementoperand erforderlich

Aber wenn ich setzen Räume in ganz a++ +und ++bdann funktioniert es gut.

int main ()
{
   int a = 5,b = 2;
   printf("%d",a++ + ++b);
   return 0;
}

Was bedeutet der Fehler im ersten Beispiel?

Barshan Das
quelle
3
Es ist nach all dieser Zeit überraschend, dass niemand entdeckt hat, dass der genaue Ausdruck, nach dem Sie fragen, als Beispiel im C99- und C11-Standard verwendet wird. Es gibt auch eine gute Erklärung. Ich habe das in meine Antwort aufgenommen.
Shafik Yaghmour
@ShafikYaghmour - Das ist 'Beispiel 2' in C11 §6.4 Lexikalische Elemente ¶6 . Es heißt "Das Programmfragment x+++++ywird analysiert als x ++ ++ + y, was eine Einschränkung für Inkrementoperatoren verletzt, obwohl die Analyse x ++ + ++ ymöglicherweise einen korrekten Ausdruck ergibt."
Jonathan Leffler

Antworten:

97

printf("%d",a+++++b);wird (a++)++ + bgemäß der Maximal Munch Rule interpretiert ! .

++(postfix) wird nicht als a ausgewertet, lvalueerfordert jedoch, dass sein Operand ein ist lvalue.

! 6.4 / 4 besagt, dass das nächste Vorverarbeitungstoken die längste Folge von Zeichen ist, die ein Vorverarbeitungstoken darstellen könnte. "

Prasoon Saurav
quelle
181

Compiler werden schrittweise geschrieben. Die erste Stufe heißt Lexer und verwandelt Zeichen in eine symbolische Struktur. So wird "++" so etwas wie ein enum SYMBOL_PLUSPLUS. Später verwandelt die Parser-Stufe dies in einen abstrakten Syntaxbaum, kann jedoch die Symbole nicht ändern. Sie können den Lexer beeinflussen, indem Sie Leerzeichen einfügen (die Endsymbole, sofern sie nicht in Anführungszeichen stehen).

Normale Lexer sind gierig (mit einigen Ausnahmen), daher wird Ihr Code als interpretiert

a++ ++ +b

Die Eingabe in den Parser ist ein Strom von Symbolen, daher würde Ihr Code ungefähr so ​​aussehen:

[ SYMBOL_NAME(name = "a"), 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS_PLUS, 
  SYMBOL_PLUS, 
  SYMBOL_NAME(name = "b") 
]

Was der Parser für syntaktisch falsch hält. (BEARBEITEN basierend auf Kommentaren: Semantisch falsch, da Sie ++ nicht auf einen r-Wert anwenden können, was zu einem ++ führt.)

a+++b 

ist

a++ +b

Welches ist in Ordnung. So sind Ihre anderen Beispiele.

Lou Franco
quelle
27
+1 Gute Erklärung. Ich muss allerdings nicht auswählen: Es ist syntaktisch korrekt, es hat nur einen semantischen Fehler (Versuch, den daraus resultierenden l-Wert zu erhöhen a++).
7
a++ergibt einen rwert.
Femaref
9
Im Zusammenhang mit Lexern wird der "gierige" Algorithmus normalerweise als Maximal Munch bezeichnet ( en.wikipedia.org/wiki/Maximal_munch ).
JoeG
14
Nett. Viele Sprachen haben dank gierigem Lexing ähnliche bizarre Eckfälle. Hier ist eine wirklich seltsame Sache, bei der es besser ist, den Ausdruck länger zu machen: In VBScript x = 10&987&&654&&321ist illegal, aber seltsamerweise x = 10&987&&654&&&321ist es legal.
Eric Lippert
1
Es hat nichts mit Gier zu tun und alles mit Ordnung und Vorrang. ++ ist höher als +, also werden zuerst zwei ++ ausgeführt. +++++ b ist auch + ++ ++ b und nicht ++ ++ + b. Gutschrift an @MByD für den Link.
30

Der Lexer verwendet einen sogenannten "Maximum Munch" -Algorithmus, um Token zu erstellen. Das bedeutet, dass beim Einlesen von Zeichen so lange Zeichen gelesen werden, bis auf etwas gestoßen wird, das nicht Teil des gleichen Tokens sein kann wie das, was es bereits hat (z. B. wenn es Ziffern gelesen hat, also eine Zahl, wenn es auf etwas trifft ein A, weiß er , dass kein Teil der Zahl sein kann. so stoppt und Blätter die Aim Eingangspuffer , um die Verwendung als Anfang des nächsten Token). Dieses Token wird dann an den Parser zurückgegeben.

In diesem Fall bedeutet dies, dass +++++lexed wird als a ++ ++ + b. Da das erste Nachinkrement einen r-Wert ergibt, kann das zweite nicht darauf angewendet werden, und der Compiler gibt einen Fehler aus.

Nur FWIW, in C ++ können Sie überladen operator++, um einen l-Wert zu erhalten, der dies ermöglicht. Beispielsweise:

struct bad_code { 
    bad_code &operator++(int) { 
        return *this;
    }
    int operator+(bad_code const &other) { 
        return 1;
    }
};

int main() { 
    bad_code a, b;

    int c = a+++++b;
    return 0;
}

Das kompiliert und läuft (obwohl es nichts tut) mit den C ++ - Compilern, die ich zur Hand habe (VC ++, g ++, Comeau).

Jerry Sarg
quelle
1
„zB wenn es Ziffern gelesen , so was hat es eine Zahl ist, wenn es ein A trifft, weiß er , dass kein Teil der Zahl sein kann“ 16FAist ein völlig in Ordnung hexadezimale Zahl , die ein A enthält
orlp
1
@nightcracker: Ja, aber ohne eine 0xam Anfang wird das immer noch wie 16folgt behandelt FA, keine einzige Hexadezimalzahl.
Jerry Coffin
@ Jerry Coffin: Du hast nicht gesagt, dass 0xes nicht Teil der Nummer ist.
Orlp
@nightcracker: nein, habe ich nicht - da die meisten Leute keine xZiffer in Betracht ziehen , schien es ziemlich unnötig.
Jerry Coffin
14

Dieses genaue Beispiel wird im Entwurf der C99-Norm ( gleiche Details in C11 ) in Abschnitt 6.4, Abschnitt 4 der lexikalischen Elemente , behandelt, in dem es heißt:

Wenn der Eingabestream bis zu einem bestimmten Zeichen in Vorverarbeitungstoken analysiert wurde, ist das nächste Vorverarbeitungstoken die längste Folge von Zeichen, die ein Vorverarbeitungstoken darstellen könnten. [...]

Dies wird auch als maximale Munch-Regel bezeichnet, die in der lexikalischen Analyse verwendet wird, um Mehrdeutigkeiten zu vermeiden. Dabei werden so viele Elemente wie möglich verwendet, um ein gültiges Token zu bilden.

Der Absatz enthält auch zwei Beispiele. Das zweite entspricht genau Ihrer Frage und lautet wie folgt:

BEISPIEL 2 Das Programmfragment x +++++ y wird als x ++ ++ + y analysiert, was eine Einschränkung für Inkrementoperatoren verletzt, obwohl die Analyse x ++ + ++ y möglicherweise einen korrekten Ausdruck ergibt.

was uns sagt, dass:

a+++++b

wird analysiert als:

a ++ ++ + b

Dies verstößt gegen die Einschränkungen für das Nachinkrement, da das Ergebnis des ersten Nachinkrements ein r-Wert ist und das Nachinkrement einen l-Wert erfordert. Dies wird im Abschnitt 6.5.2.4 Postfix-Inkrementierungs- und -Dekrementierungsoperatoren behandelt, der besagt ( Hervorhebung von mir ):

Der Operand des Postfix-Inkrement- oder Dekrement-Operators muss einen qualifizierten oder nicht qualifizierten Real- oder Zeigertyp haben und ein modifizierbarer Wert sein.

und

Das Ergebnis des Postfix ++ - Operators ist der Wert des Operanden.

Das Buch C ++ Gotchas behandelt diesen Fall auch in Gotcha #17 Maximal Munch Problems. Es ist das gleiche Problem auch in C ++ und enthält auch einige Beispiele. Es erklärt, dass beim Umgang mit den folgenden Zeichensätzen:

->*

Der lexikalische Analysator kann eines von drei Dingen ausführen:

  • Behandeln Sie es wie drei Token: -, >und*
  • Behandle es als zwei Token: ->und*
  • Behandle es als ein Zeichen: ->*

Die maximale Munch- Regel ermöglicht es, diese Mehrdeutigkeiten zu vermeiden. Der Autor weist darauf hin, dass es ( im C ++ - Kontext ):

löst viel mehr Probleme als es verursacht, aber in zwei häufigen Situationen ist es ein Ärger.

Das erste Beispiel wären Vorlagen, deren Vorlagenargumente auch Vorlagen sind ( die in C ++ 11 gelöst wurden ), zum Beispiel:

list<vector<string>> lovos; // error!
                  ^^

Welche interpretiert die Schließwinkel Klammern als Shift - Operator , und so ein Raum eindeutig zu machen ist erforderlich:

list< vector<string> > lovos;
                    ^

Der zweite Fall betrifft Standardargumente für Zeiger, zum Beispiel:

void process( const char *= 0 ); // error!
                         ^^

würde als *=Zuweisungsoperator interpretiert werden, besteht die Lösung in diesem Fall darin, die Parameter in der Deklaration zu benennen.

Shafik Yaghmour
quelle
Wissen Sie, welcher Teil von C ++ 11 die maximale Munching-Regel angibt? 2.2.3, 2.5.3 sind interessant, aber nicht so explizit wie C. Die >>Regel wird gefragt unter: stackoverflow.com/questions/15785496/…
Ciro Santilli 9 冠状 病 六四 事件 9
1
@CiroSantilli this 文件 六四 事件 法轮功 sehen diese Antwort hier
Shafik Yaghmour
Schön, danke, es ist einer der Abschnitte, auf die ich hingewiesen habe. Ich werde dich morgen abstimmen, wenn meine Mütze
abgenutzt ist ;-)
12

Ihr Compiler versucht verzweifelt zu analysieren a+++++bund interpretiert es als (a++)++ +b. Das Ergebnis von post-increment ( a++) ist kein l-Wert , dh es kann nicht erneut nachinkrementiert werden.

Bitte schreiben Sie niemals solchen Code in Produktionsqualitätsprogramme. Denken Sie an den armen Kerl, der nach Ihnen kommt und Ihren Code interpretieren muss.

Péter Török
quelle
10
(a++)++ +b

a ++ gibt den vorherigen Wert zurück, einen r-Wert. Sie können dies nicht erhöhen.

Erik
quelle
7

Weil es undefiniertes Verhalten verursacht.

Welches ist es?

c = (a++)++ + b
c = (a) + ++(++b)
c = (a++) + (++b)

Ja, weder Sie noch der Compiler wissen es.

BEARBEITEN:

Der wahre Grund ist der, wie von den anderen gesagt:

Es wird interpretiert als (a++)++ + b.

Nach dem Inkrementieren ist jedoch ein l-Wert erforderlich (eine Variable mit einem Namen). (a ++) gibt jedoch einen r-Wert zurück, der nicht inkrementiert werden kann, was zu der Fehlermeldung führt, die Sie erhalten.

Vielen Dank an die anderen, um darauf hinzuweisen.

RedX
quelle
5
man könnte dasselbe für a +++ b sagen - (a ++) + b und a + (++ b) haben unterschiedliche Ergebnisse.
Michael Chinen
4
a+++ba++ + b
Tatsächlich
4
Ich denke nicht, dass dies die richtige Antwort ist, aber ich könnte mich irren. Ich denke, der Lexer definiert es als etwas, a++ ++ +bdas nicht analysiert werden kann.
Lou Franco
2
Ich bin mit dieser Antwort nicht einverstanden. 'undefiniertes Verhalten' unterscheidet sich stark von der Mehrdeutigkeit der Tokenisierung. und ich denke auch nicht, dass das Problem ist.
Jim Blackler
2
"Sonst a +++++ b würde bewerten zu ((a ++) ++) + b" ... meiner Meinung nach ist jetzt a+++++b nicht zu bewerten (a++)++)+b. Wenn Sie diese Klammern einfügen und neu erstellen, ändert sich bei GCC die Fehlermeldung nicht.
Jim Blackler
5

Ich denke, der Compiler sieht es als

c = ((a ++) ++) + b

++muss als Operanden einen Wert haben, der geändert werden kann. a ist ein Wert, der geändert werden kann. a++ist jedoch ein 'rvalue', kann nicht geändert werden.

Übrigens ist der Fehler, den ich auf GCC C sehe, der gleiche, aber anders formuliert : lvalue required as increment operand.

Jim Blackler
quelle
0

Befolgen Sie diese Reihenfolge

1. ++ (Pre-Inkrement)

2. + - (Addition oder Subtraktion)

3. "x" + "y" addieren beide Sequenzen

int a = 5,b = 2; printf("%d",a++ + ++b); //a is 5 since it is post increment b is 3 pre increment return 0; //it is 5+3=8

Rakshit ks
quelle