¿Por qué no funciona a +++++ b?

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

Este código da el siguiente error:

error: lvalue requerido como operando de incremento

Pero si pongo espacios en todas partes a++ +y ++b, entonces funciona bien.

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

¿Qué significa el error en el primer ejemplo?

Barshan Das
fuente
3
Es sorprendente después de todo este tiempo que nadie haya descubierto que la expresión exacta sobre la que está preguntando se usa como ejemplo en el estándar C99 y C11. También da una buena explicación. Lo he incluido en mi respuesta.
Shafik Yaghmour
@ShafikYaghmour - Ese es el 'Ejemplo 2' en C11 §6.4 Elementos léxicos ¶6 . Dice "El fragmento de programa x+++++yse analiza como x ++ ++ + y, lo que viola una restricción sobre los operadores de incremento, aunque el análisis x ++ + ++ ypodría producir una expresión correcta".
Jonathan Leffler

Respuestas:

97

printf("%d",a+++++b);se interpreta de (a++)++ + bacuerdo con la Regla de Máximo Munch ! .

++(sufijo) no se evalúa como an, lvaluepero requiere que su operando sea un lvalue.

! 6.4 / 4 dice que el siguiente token de preprocesamiento es la secuencia más larga de caracteres que podría constituir un token de preprocesamiento "

Prasoon Saurav
fuente
181

Los compiladores se escriben en etapas. La primera etapa se llama lexer y convierte a los personajes en una estructura simbólica. Entonces "++" se convierte en algo así como un enum SYMBOL_PLUSPLUS. Más tarde, la etapa del analizador lo convierte en un árbol de sintaxis abstracto, pero no puede cambiar los símbolos. Puede afectar al lexer insertando espacios (cuyos símbolos finales a menos que estén entre comillas).

Los lexers normales son codiciosos (con algunas excepciones), por lo que su código se interpreta como

a++ ++ +b

La entrada al analizador es un flujo de símbolos, por lo que su código sería algo como:

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

Lo que el analizador piensa que es sintácticamente incorrecto. (EDITAR basado en comentarios: semánticamente incorrecto porque no puede aplicar ++ a un valor r, lo que resulta en un ++)

a+++b 

es

a++ +b

Que esta bien. También lo son sus otros ejemplos.

Lou Franco
fuente
27
+1 Buena explicación. Sin embargo, tengo que ser quisquilloso: es sintácticamente correcto, solo tiene un error semántico (intento de incrementar el lvalue resultante de a++).
7
a++da como resultado un valor r.
Femaref
9
En el contexto de los lexers, el algoritmo 'codicioso' generalmente se llama Maximal Munch ( en.wikipedia.org/wiki/Maximal_munch ).
JoeG
14
Agradable. Muchos idiomas tienen casos de esquina extraños similares gracias al lexing codicioso. Aquí hay uno realmente extraño en el que alargar la expresión la hace mejor: en VBScript x = 10&987&&654&&321es ilegal, pero extrañamente x = 10&987&&654&&&321es legal.
Eric Lippert
1
No tiene nada que ver con la codicia y todo que ver con el orden y la precedencia. ++ es mayor que +, por lo que primero se realizarán dos ++. +++++ b también será + ++ ++ by no ++ ++ + b. Crédito a @MByD por el enlace.
30

El lexer usa lo que generalmente se llama un algoritmo de "masticación máxima" para crear tokens. Eso significa que mientras lee caracteres, sigue leyendo caracteres hasta que encuentra algo que no puede ser parte del mismo token que el que ya tiene (por ejemplo, si ha estado leyendo dígitos, entonces lo que tiene es un número, si encuentra an A, sabe que no puede ser parte del número, por lo que se detiene y deja Aen el búfer de entrada para usarlo como el comienzo del siguiente token). Luego devuelve ese token al analizador.

En este caso, eso significa que +++++se lexiza como a ++ ++ + b. Dado que el primer incremento posterior produce un rvalue, el segundo no se puede aplicar y el compilador da un error.

Solo FWIW, en C ++ puede sobrecargar operator++para producir un lvalue, lo que permite que esto funcione. Por ejemplo:

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

Se compila y se ejecuta (aunque no hace nada) con los compiladores de C ++ que tengo a mano (VC ++, g ++, Comeau).

Jerry Coffin
fuente
1
"Por ejemplo, si ha estado leyendo dígitos, entonces lo que tiene es un número, si encuentra una A, sabe que no puede ser parte del número" 16FAes un número hexadecimal perfectamente
correcto
1
@nightcracker: sí, pero sin un 0xal principio, lo tratará como 16seguido de FA, ni un solo número hexadecimal.
Jerry Coffin
@ Jerry Coffin: No dijiste que no 0xformaba parte del número.
orlp
@nightcracker: no, no lo hice, dado que la mayoría de la gente no considera xun dígito, parecía bastante innecesario.
Jerry Coffin
14

Este ejemplo exacto está cubierto en el borrador de la norma C99 (los mismos detalles en C11 ) sección 6.4 Elementos léxicos párrafo 4 que dice:

Si el flujo de entrada se ha analizado en tokens de preprocesamiento hasta un carácter determinado, el siguiente token de preprocesamiento es la secuencia más larga de caracteres que podría constituir un token de preprocesamiento. [...]

que también se conoce como la regla de munch máxima que se utiliza en el análisis léxico para evitar ambigüedades y funciona tomando tantos elementos como sea posible para formar un token válido.

el párrafo también tiene dos ejemplos, el segundo coincide exactamente con su pregunta y es el siguiente:

EJEMPLO 2 El fragmento de programa x +++++ y se analiza como x ++ ++ + y, lo que viola una restricción sobre los operadores de incremento, aunque el análisis x ++ + ++ y podría producir una expresión correcta.

que nos dice que:

a+++++b

se analizará como:

a ++ ++ + b

lo que viola las restricciones sobre el incremento posterior, ya que el resultado del primer incremento posterior es un valor r y el incremento posterior requiere un valor l. Esto se cubre en la sección 6.5.2.4 Operadores de incremento y decremento de Postfix que dice ( énfasis mío ):

El operando del operador de incremento o decremento sufijo tendrá un tipo real o puntero calificado o no calificado y será un valor modificable.

y

El resultado del operador postfix ++ es el valor del operando.

El libro C ++ Gotchas también cubre este caso en Gotcha #17 Maximal Munch Problems , es el mismo problema en C ++ y también da algunos ejemplos. Explica eso cuando se trata del siguiente conjunto de caracteres:

->*

el analizador léxico puede hacer una de estas tres cosas:

  • Tratarlo como tres fichas: -, >y*
  • Trátelo como dos tokens: ->y*
  • Trátelo como una ficha: ->*

La regla de munch máximo le permite evitar estas ambigüedades. El autor señala que ( en el contexto de C ++ ):

resuelve muchos más problemas de los que causa, pero en dos situaciones comunes, es una molestia.

El primer ejemplo serían plantillas cuyos argumentos de plantilla también son plantillas ( que se resolvió en C ++ 11 ), por ejemplo:

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

Lo que interpreta los corchetes angulares de cierre como el operador de cambio , por lo que se requiere un espacio para eliminar la ambigüedad:

list< vector<string> > lovos;
                    ^

El segundo caso involucra argumentos predeterminados para punteros, por ejemplo:

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

se interpretaría como *=operador de asignación, la solución en este caso es nombrar los parámetros en la declaración.

Shafik Yaghmour
fuente
¿Sabes qué parte de C ++ 11 dice la regla de masticación máxima? 2.2.3, 2.5.3 son interesantes, pero no tan explícitos como C. La >>regla se solicita en: stackoverflow.com/questions/15785496/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@CiroSantilli 巴拿馬 文件 六四 事件 法轮功 vea esta respuesta aquí
Shafik Yaghmour
Gracias, es una de las secciones que señalé. Te votaré mañana, cuando mi gorra se
acabe
12

Su compilador intenta desesperadamente analizar a+++++by lo interpreta como (a++)++ +b. Ahora, el resultado del post-incremento ( a++) no es un valor l , es decir, no se puede volver a incrementar posteriormente.

Por favor, nunca escriba ese código en programas de calidad de producción. Piense en el pobre tipo que viene detrás de usted y necesita interpretar su código.

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

a ++ devuelve el valor anterior, un rvalue. No puedes incrementar esto.

Erik
fuente
7

Porque provoca un comportamiento indefinido.

¿Cuál es?

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

Sí, ni usted ni el compilador lo saben.

EDITAR:

La verdadera razón es la que dijeron los demás:

Se interpreta como (a++)++ + b.

pero el incremento posterior requiere un lvalue (que es una variable con un nombre) pero (a ++) devuelve un rvalue que no se puede incrementar, lo que lleva al mensaje de error que aparece.

Gracias a los demás por señalar esto.

RedX
fuente
5
se podría decir lo mismo para a +++ b - (a ++) + b y a + (++ b) tienen resultados diferentes.
Michael Chinen
4
en realidad, postfix ++ tiene mayor prioridad que prefix ++, por a+++blo que siempre lo esa++ + b
MByD
4
No creo que esta sea la respuesta correcta, pero podría estar equivocado. Creo que el lexer lo define como lo a++ ++ +bque no se puede analizar.
Lou Franco
2
No estoy de acuerdo con esta respuesta. "comportamiento indefinido" es bastante diferente de la ambigüedad de la tokenización; y no creo que el problema tampoco lo sea.
Jim Blackler
2
"De lo contrario un +++++ b evaluaría a ((A ++) ++) + b" ... mi punto de vista en este momento es a+++++b no evaluar a (a++)++)+b. Ciertamente, con GCC, si inserta esos corchetes y reconstruye, el mensaje de error no cambia.
Jim Blackler
5

Creo que el compilador lo ve como

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

++debe tener como operando un valor modificable. a es un valor que se puede modificar. a++sin embargo, es un 'rvalue', no se puede modificar.

Por cierto, el error que veo en GCC C es el mismo, pero de manera diferente redactada-: lvalue required as increment operand.

Jim Blackler
fuente
0

Siga este orden de precedencia

1. ++ (incremento previo)

2. + - (suma o resta)

3. "x" + "y" suman la secuencia

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
fuente