Perché a +++++ b non funziona?


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

Questo codice restituisce il seguente errore:

errore: lvalore richiesto come operando di incremento

Ma se inserisco spazi ovunque a++ +e ++b, allora funziona bene.

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

Cosa significa l'errore nel primo esempio?


3
È sorprendente, dopo tutto questo tempo, che nessuno avesse scoperto che l'esatta espressione di cui chiedi è usata come esempio negli standard C99 e C11. Fornisce anche una buona spiegazione. L'ho incluso nella mia risposta.
Shafik Yaghmour

@ShafikYaghmour - Questo è l '"Esempio 2" in C11 §6.4 Elementi lessicali ¶6 . Dice "Il frammento di programma x+++++yviene analizzato come x ++ ++ + y, il che viola un vincolo sugli operatori di incremento, anche se l'analisi x ++ + ++ ypotrebbe produrre un'espressione corretta."
Jonathan Leffler

Risposte:


97

printf("%d",a+++++b);è interpretato come (a++)++ + bsecondo la regola di Maximal Munch ! .

++(postfix) non restituisce un lvaluema richiede che il suo operando sia un lvalue.

! 6.4 / 4 dice che il prossimo token di pre-elaborazione è la sequenza più lunga di caratteri che potrebbe costituire un token di pre-elaborazione "


181

I compilatori vengono scritti in più fasi. La prima fase si chiama lexer e trasforma i personaggi in una struttura simbolica. Quindi "++" diventa qualcosa come un file enum SYMBOL_PLUSPLUS. Successivamente, la fase del parser lo trasforma in un albero di sintassi astratto, ma non può modificare i simboli. Puoi influenzare il lexer inserendo spazi (che terminano con i simboli a meno che non siano tra virgolette).

I lexer normali sono avidi (con alcune eccezioni), quindi il tuo codice viene interpretato come

a++ ++ +b

L'input per il parser è un flusso di simboli, quindi il tuo codice sarebbe qualcosa del tipo:

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

Che il parser pensa sia sintatticamente errato. (MODIFICA basata sui commenti: semanticamente errato perché non è possibile applicare ++ a un valore r, che risulta in a ++)

a+++b 

è

a++ +b

Che va bene. Così sono i tuoi altri esempi.


27
+1 Buona spiegazione. Devo fare il pelo nell'uovo però: è sintatticamente corretto, ha solo un errore semantico (prova ad aumentare il valore risultante da a++).

7
a++si traduce in un rvalue.
Femaref

9
Nel contesto dei lexer, l'algoritmo "avido" è solitamente chiamato Maximal Munch ( en.wikipedia.org/wiki/Maximal_munch ).
JoeG

14
Bello. Molte lingue hanno casi d'angolo bizzarri simili grazie al lexing avido. Eccone uno davvero strano in cui rendere l'espressione più lunga la rende migliore: in VBScript x = 10&987&&654&&321è illegale, ma stranamente x = 10&987&&654&&&321è legale.
Eric Lippert

1
Non ha nulla a che fare con l'avidità e tutto a che fare con l'ordine e la precedenza. ++ è più alto di + quindi verranno eseguiti prima due ++. +++++ b sarà anche + ++ ++ be non ++ ++ + b. Ringraziamo @MByD per il collegamento.

30

Il lexer utilizza quello che viene generalmente chiamato un algoritmo di "munch massimo" per creare token. Ciò significa che mentre sta leggendo i caratteri, continua a leggere i caratteri finché non incontra qualcosa che non può far parte dello stesso token di quello che ha già (ad esempio, se sta leggendo cifre quindi quello che ha è un numero, se incontra an A, sa che non può essere parte del numero, quindi si ferma e lascia il Anel buffer di input da utilizzare come inizio del token successivo). Quindi restituisce quel token al parser.

In questo caso, ciò significa che +++++viene lexed come a ++ ++ + b. Poiché il primo post-incremento restituisce un valore, il secondo non può essere applicato e il compilatore restituisce un errore.

Solo FWIW, in C ++ puoi eseguire l'overload operator++per produrre un lvalue, che consente a questo di funzionare. Per esempio:

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;
}

Lo compila e funziona (anche se non fa nulla) con i compilatori C ++ che ho a portata di mano (VC ++, g ++, Comeau).


1
"Ad esempio, se sta leggendo cifre, quindi quello che ha è un numero, se incontra una A, sa che non può essere parte del numero" 16FAè un numero esadecimale perfettamente
corretto

1
@nightcracker: sì, ma senza un 0xall'inizio lo tratterà comunque come 16seguito da FA, non un singolo numero esadecimale.
Jerry Coffin

@ Jerry Coffin: Non hai detto che 0xnon faceva parte del numero.
orlp

@nightcracker: no, non l'ho fatto - dato che la maggior parte delle persone non considera xuna cifra, sembrava abbastanza inutile.
Jerry Coffin

14

Questo esempio esatto è trattato nella bozza dello standard C99 ( stessi dettagli in C11 ) sezione 6.4 Elementi lessicali paragrafo 4 che in dice:

Se il flusso di input è stato analizzato in token di pre-elaborazione fino a un dato carattere, il token di pre-elaborazione successivo è la sequenza di caratteri più lunga che potrebbe costituire un token di pre-elaborazione. [...]

che è anche conosciuta come la regola del munch massimale che viene utilizzata nell'analisi lessicale per evitare ambiguità e funziona prendendo il maggior numero di elementi possibile per formare un segno valido.

il paragrafo ha anche due esempi il secondo è una corrispondenza esatta per la tua domanda ed è il seguente:

ESEMPIO 2 Il frammento di programma x +++++ y viene analizzato come x ++ ++ + y, che viola un vincolo sugli operatori di incremento, anche se l'analisi x ++ + ++ y potrebbe produrre un'espressione corretta.

che ci dice che:

a+++++b

sarà analizzato come:

a ++ ++ + b

che viola i vincoli sull'incremento di post poiché il risultato del primo incremento di post è un rvalue e l'incremento di post richiede un lvalue. Questo è trattato nella sezione 6.5.2.4 Operatori di incremento e decremento di Postfix che dice ( enfasi mia ):

L'operando dell'operatore di incremento o decremento postfisso deve avere un tipo reale o puntatore qualificato o non qualificato e deve essere un valore modificabile.

e

Il risultato dell'operatore postfisso ++ è il valore dell'operando.

Il libro C ++ Gotchas copre anche questo caso in Gotcha #17 Maximal Munch Problems , è lo stesso problema anche in C ++ e fornisce anche alcuni esempi. Spiega che quando si ha a che fare con il seguente set di caratteri:

->*

l'analizzatore lessicale può fare una di tre cose:

  • Trattarlo come tre gettoni: -, >e*
  • Trattalo come due gettoni: ->e*
  • Trattalo come un token: ->*

La regola del munch massimo consente di evitare queste ambiguità. L'autore sottolinea che ( nel contesto C ++ ):

risolve molti più problemi di quanti ne provoca, ma in due situazioni comuni è un fastidio.

Il primo esempio sarebbero i modelli i cui argomenti del modello sono anche modelli ( che è stato risolto in C ++ 11 ), ad esempio:

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

Che interpreta le parentesi angolari di chiusura come l' operatore di spostamento , quindi è necessario uno spazio per disambiguare:

list< vector<string> > lovos;
                    ^

Il secondo caso riguarda argomenti predefiniti per i puntatori, ad esempio:

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

verrebbe interpretato come *=operatore di assegnazione, la soluzione in questo caso è nominare i parametri nella dichiarazione.


Sai quale parte di C ++ 11 dice la regola del massimo sgranocchiare? 2.2.3, 2.5.3 sono interessanti, ma non così esplicito come C. La >>regola è chiesto a: stackoverflow.com/questions/15785496/...
Ciro Santilli郝海东冠状病六四事件法轮功

1
@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 vedi questa risposta qui
Shafik Yaghmour

Grazie mille, è una delle sezioni che ho indicato. Ti voterò domani quando il mio berretto
svanirà

12

Il tuo compilatore cerca disperatamente di analizzarlo a+++++be lo interpreta come (a++)++ +b. Ora, il risultato di post-increment ( a++) non è un lvalue , cioè non può essere post-incrementato di nuovo.

Per favore, non scrivere mai questo codice nei programmi di qualità della produzione. Pensa al poveretto che ti viene dietro e che ha bisogno di interpretare il tuo codice.


10
(a++)++ +b

a ++ restituisce il valore precedente, un rvalue. Non puoi incrementarlo.


7

Perché provoca un comportamento indefinito.

Qual é?

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

Sì, né tu né il compilatore lo sapete.

MODIFICARE:

Il vero motivo è quello come detto dagli altri:

Viene interpretato come (a++)++ + b.

ma post increment richiede un lvalue (che è una variabile con un nome) ma (a ++) restituisce un rvalue che non può essere incrementato portando così al messaggio di errore che ottieni.

Grazie agli altri per averlo fatto notare.


5
si potrebbe dire lo stesso per a +++ b - (a ++) + be a + (++ b) hanno risultati diversi.
Michael Chinen

4
in realtà, postfix ++ ha una precedenza maggiore del prefisso ++, quindi a+++bè semprea++ + b
MByD

4
Non credo che questa sia la risposta giusta, ma potrei sbagliarmi. Penso che il lexer lo definisca come a++ ++ +bnon analizzabile.
Lou Franco

2
Non sono d'accordo con questa risposta. "comportamento indefinito" è molto diverso dall'ambiguità della tokenizzazione; e non credo che il problema sia neanche questo.
Jim Blackler

2
"In caso contrario, un +++++ b sarebbe valutato come ((A ++) ++) + b" ... mio punto di vista in questo momento è a+++++b non valutare a (a++)++)+b. Certamente con GCC se inserisci quelle parentesi e ricostruisci, il messaggio di errore non cambia.
Jim Blackler

5

Penso che il compilatore lo veda come

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

++deve avere come operando un valore modificabile. a è un valore che può essere modificato. a++tuttavia è un "valore", non può essere modificato.

Tra l'altro l'errore che vedo su GCC C è lo stesso, ma in modo diverso-formulata: lvalue required as increment operand.


0

Segui questo ordine di precisione

1. ++ (pre incremento)

2. + - (addizione o sottrazione)

3. "x" + "y" aggiungono entrambe le sequenze

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.