Pourquoi a +++++ b ne fonctionne-t-il pas?

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

Ce code donne l'erreur suivante:

erreur: lvalue requise comme opérande d'incrément

Mais si je mets des espaces partout a++ +et ++b, alors cela fonctionne bien.

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

Que signifie l'erreur dans le premier exemple?

Barshan Das
la source
3
Il est surprenant après tout ce temps que personne n'ait découvert que l'expression exacte que vous demandez est utilisée comme exemple dans la norme C99 et C11. Cela donne également une bonne explication. J'ai inclus cela dans ma réponse.
Shafik Yaghmour
@ShafikYaghmour - C'est «l'exemple 2» dans C11 §6.4 Éléments lexicaux ¶6 . Il dit "Le fragment de programme x+++++yest analysé comme x ++ ++ + y, ce qui viole une contrainte sur les opérateurs d'incrémentation, même si l'analyse x ++ + ++ ypeut produire une expression correcte."
Jonathan Leffler

Réponses:

97

printf("%d",a+++++b);est interprété comme (a++)++ + bselon la règle Maximal Munch ! .

++(postfix) n'évalue pas à un lvaluemais il nécessite que son opérande soit un lvalue.

! 6.4 / 4 indique que le prochain jeton de prétraitement est la plus longue séquence de caractères qui pourrait constituer un jeton de prétraitement "

Prasoon Saurav
la source
181

Les compilateurs sont écrits par étapes. La première étape s'appelle le lexer et transforme les caractères en une structure symbolique. Donc "++" devient quelque chose comme unenum SYMBOL_PLUSPLUS . Plus tard, l'étape de l'analyseur transforme cela en un arbre de syntaxe abstrait, mais il ne peut pas changer les symboles. Vous pouvez affecter le lexer en insérant des espaces (qui terminent les symboles sauf s'ils sont entre guillemets).

Les lexers normaux sont gourmands (à quelques exceptions près), donc votre code est interprété comme

a++ ++ +b

L'entrée de l'analyseur est un flux de symboles, donc votre code serait quelque chose comme:

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

Ce que l'analyseur pense est syntaxiquement incorrect. (EDIT basé sur les commentaires: sémantiquement incorrect car vous ne pouvez pas appliquer ++ à une valeur r, ce qui entraîne un ++)

a+++b 

est

a++ +b

Ce qui est ok. Vos autres exemples aussi.

Lou Franco
la source
27
+1 Bonne explication. Je dois cependant pinailler: il est syntaxiquement correct, il a juste une erreur sémantique (tentative d'incrémenter la lvalue résultant de a++).
7
a++donne une rvalue.
Femaref
9
Dans le contexte des lexers, l'algorithme «gourmand» est généralement appelé Maximal Munch ( en.wikipedia.org/wiki/Maximal_munch ).
JoeG
14
Agréable. De nombreuses langues ont des cas de coin bizarres similaires grâce au lexing gourmand. En voici un vraiment étrange où rendre l'expression plus longue la rend meilleure: dans VBScript x = 10&987&&654&&321est illégal, mais curieusement x = 10&987&&654&&&321c'est légal.
Eric Lippert
1
Cela n'a rien à voir avec la cupidité et tout à voir avec l'ordre et la préséance. ++ est supérieur alors + donc deux ++ seront effectués en premier. +++++ b sera également + ++ ++ b et non ++ ++ + b. Crédit à @MByD pour le lien.
30

Le lexer utilise ce que l'on appelle généralement un algorithme "maximum munch" pour créer des jetons. Cela signifie que lorsqu'il lit des caractères, il continue à lire des caractères jusqu'à ce qu'il rencontre quelque chose qui ne peut pas faire partie du même jeton que ce qu'il a déjà (par exemple, s'il a lu des chiffres, alors ce qu'il a est un nombre, s'il rencontre an A, il sait que cela ne peut pas faire partie du nombre. Il s'arrête donc et laisse leA dans le tampon d'entrée pour l'utiliser comme début du jeton suivant). Il renvoie ensuite ce jeton à l'analyseur.

Dans ce cas, cela signifie qu'il +++++est défini comme a ++ ++ + b. Puisque le premier post-incrémentation produit une rvalue, la seconde ne peut pas lui être appliquée et le compilateur donne une erreur.

Juste FWIW, en C ++, vous pouvez surcharger operator++pour générer une lvalue, ce qui permet à cela de fonctionner. Par exemple:

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

Le compile et s'exécute (bien qu'il ne fasse rien) avec les compilateurs C ++ que j'ai sous la main (VC ++, g ++, Comeau).

Jerry Coffin
la source
1
"par exemple, s'il a lu des chiffres alors ce qu'il a est un nombre, s'il rencontre un A, il sait qu'il ne peut pas faire partie du nombre" 16FAest un nombre hexadécimal parfaitement fin qui contient un A.
orlp
1
@nightcracker: oui, mais sans un 0xau début, il traitera toujours cela comme 16suivi par FA, pas un seul nombre hexadécimal.
Jerry Coffin
@Jerry Coffin: Vous n'avez pas dit qu'il 0xne faisait pas partie du nombre.
orlp
@nightcracker: non, je ne l'ai pas fait - étant donné que la plupart des gens ne considèrent pas xun chiffre, cela semblait tout à fait inutile.
Jerry Coffin
14

Cet exemple exact est couvert dans le projet de norme C99 ( mêmes détails dans C11 ) section 6.4 Éléments lexicaux paragraphe 4 qui dit:

Si le flux d'entrée a été analysé en jetons de prétraitement jusqu'à un caractère donné, le jeton de prétraitement suivant est la plus longue séquence de caractères pouvant constituer un jeton de prétraitement. [...]

qui est également connue sous le nom de règle de munch maximal qui est utilisée dans l'analyse lexicale pour éviter les ambiguïtés et fonctionne en prenant autant d'éléments que possible pour former un jeton valide.

le paragraphe contient également deux exemples, le second correspond exactement à votre question et se présente comme suit:

EXEMPLE 2 Le fragment de programme x +++++ y est analysé comme x ++ ++ + y, ce qui viole une contrainte sur les opérateurs d'incrémentation, même si l'analyse x ++ + ++ y peut donner une expression correcte.

ce qui nous dit que:

a+++++b

sera analysé comme suit:

a ++ ++ + b

ce qui viole les contraintes de post-incrémentation puisque le résultat du premier post-incrément est une rvalue et que post-increment nécessite une lvalue. Ceci est couvert dans la section6.5.2.4 Opérateurs d'incrémentation et de décrémentation de Postfix qui dit (c'est moi qui souligne ):

L'opérande de l'opérateur d'incrémentation ou de décrémentation postfixe doit avoir un type réel ou pointeur qualifié ou non et doit être une valeur l modifiable.

et

Le résultat de l'opérateur postfix ++ est la valeur de l'opérande.

Le livre C ++ Gotchas couvre également ce cas dans Gotcha #17 Maximal Munch Problems c'est le même problème dans C ++ et il donne également quelques exemples. Il explique que lorsqu'il s'agit de l'ensemble de caractères suivant:

->*

l'analyseur lexical peut faire l'une des trois choses suivantes:

  • Traiter comme trois jetons: -,> et*
  • Traitez-le comme deux jetons: -> et*
  • Traitez-le comme un jeton: ->*

Le munch maximal règle de permet d'éviter ces ambiguïtés. L'auteur précise qu'il ( dans le contexte C ++ ):

résout beaucoup plus de problèmes qu'il n'en cause, mais dans deux situations courantes, c'est une gêne.

Le premier exemple serait des modèles dont les arguments de modèle sont également des modèles ( ce qui a été résolu en C ++ 11 ), par exemple:

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

Ce qui interprète les crochets angulaires de fermeture comme l' opérateur de décalage , et donc un espace est nécessaire pour lever l'ambiguïté:

list< vector<string> > lovos;
                    ^

Le deuxième cas concerne les arguments par défaut pour les pointeurs, par exemple:

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

serait interprété comme un *=opérateur d'affectation, la solution dans ce cas est de nommer les paramètres dans la déclaration.

Shafik Yaghmour
la source
Savez-vous quelle partie de C ++ 11 dit la règle de grignotage maximum? 2.2.3, 2.5.3 sont intéressants, mais pas aussi explicites que C. La >>règle est posée sur: stackoverflow.com/questions/15785496/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 voir cette réponse ici
Shafik Yaghmour
Merci beaucoup, c'est l'une des sections que j'ai mentionnées. Je vous voterai pour demain quand ma casquette sera usée ;-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
12

Votre compilateur essaie désespérément d'analyser a+++++bet l'interprète comme (a++)++ +b. Maintenant, le résultat du post-incrément ( a++) n'est pas une lvalue , c'est-à-dire qu'il ne peut pas être post-incrémenté à nouveau.

Veuillez ne jamais écrire un tel code dans des programmes de qualité de production. Pensez au pauvre type qui vient après vous qui a besoin d'interpréter votre code.

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

a ++ renvoie la valeur précédente, une rvalue. Vous ne pouvez pas augmenter cela.

Erik
la source
7

Parce que cela provoque un comportement indéfini.

Laquelle est-ce?

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

Ouais, ni vous ni le compilateur ne le savez.

ÉDITER:

La vraie raison est celle que disent les autres:

Il est interprété comme (a++)++ + b.

mais post-incrémentation nécessite une lvalue (qui est une variable avec un nom) mais (a ++) renvoie une rvalue qui ne peut pas être incrémentée conduisant ainsi au message d'erreur que vous obtenez.

Merci aux autres de le signaler.

RedX
la source
5
vous pourriez dire la même chose pour a +++ b - (a ++) + b et a + (++ b) ont des résultats différents.
Michael Chinen
4
en fait, postfix ++ a une priorité plus élevée que prefix ++, il en a+++best toujours de mêmea++ + b
MByD
4
Je ne pense pas que ce soit la bonne réponse, mais je peux me tromper. Je pense que le lexer le définit comme étant a++ ++ +bqui ne peut pas être analysé.
Lou Franco
2
Je ne suis pas d'accord avec cette réponse. «comportement indéfini» est assez différent de l'ambiguïté de la tokenisation; et je ne pense pas que le problème soit non plus.
Jim Blackler
2
" Dans le cas contraire un b +++++ évaluerait à ((a ++) ++) + b" ... mon avis est en ce moment n'évalue à . Certainement avec GCC si vous insérez ces crochets et reconstruisez, le message d'erreur ne change pas. a+++++b (a++)++)+b
Jim Blackler
5

Je pense que le compilateur le voit comme

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

++doit avoir comme opérande une valeur modifiable. a est une valeur qui peut être modifiée. a++cependant est une 'rvalue', elle ne peut pas être modifiée.

Soit dit en passant l'erreur que je vois sur GCC C est le même, mais différemment rédigé: lvalue required as increment operand.

Jim Blackler
la source
0

Suivez cet ordre préalable

1. ++ (pré-incrément)

2. + - (addition ou soustraction)

3. "x" + "y" ajouter à la fois la séquence

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
la source