¿Cuál es el resultado de + = en C y C ++?

93

Tengo el siguiente código:

#include <stdio.h>
int main(int argc, char **argv) {
    int i = 0;
    (i+=10)+=10;
    printf("i = %d\n", i);
    return 0;
}

Si intento compilarlo como fuente C usando gcc, aparece un error:

error: lvalue required as left operand of assignment

Pero si lo compilo como una fuente de C ++ usando g ++ no obtengo ningún error y cuando ejecuto el ejecutable:

i = 20

¿Por qué el comportamiento diferente?

Svetlin Mladenov
fuente
85
¿Lenguaje diferente, reglas de sintaxis diferentes ?. Personalmente, rechazaría ese código en la revisión del código.
Máximo
7
Evite un código como este en mi opinión ... Poco claro para todos.
allaire
1
Sin duda, el código no está limpio y debe evitarse en el desarrollo "real". Sin embargo, observo el mismo comportamiento y me gustaría saber las razones.
ulidtko
9
Este NO es un extracto de código de un software real. Esto es solo un problema con el que me he topado accidentalmente.
Svetlin Mladenov
3
@JohnDibling Creo que el voto positivo es específicamente el (i + = 10) + = 10. No sé en qué lenguaje es el código legítimo y el hecho de que él dice que C ++ en realidad lo compila me intriga.
Tony318

Respuestas:

133

La semántica de los operadores de asignación compuesta es diferente en C y C ++:

Estándar C99, 6.5.16, parte 3:

Un operador de asignación almacena un valor en el objeto designado por el operando izquierdo. Una expresión de asignación tiene el valor del operando izquierdo después de la asignación, pero no es un valor l.

En C ++ 5.17.1:

El operador de asignación (=) y los operadores de asignación compuesta se agrupan de derecha a izquierda. Todos requieren un lvalor modificable como su operando izquierdo y devuelven un lvalue con el tipo y valor del operando izquierdo después de que se ha realizado la asignación.

EDITAR: El comportamiento de (i+=10)+=10en C ++ no está definido en C ++ 98, pero está bien definido en C ++ 11. Consulte esta respuesta a la pregunta de NPE para conocer las partes relevantes de los estándares.

dasblinkenlight
fuente
Correcto. Uno devuelve el valor del resultado y el otro devuelve la variable (dirección)
texasbruce
7
Importante : (i+=10)+=10tenga en cuenta que es un comportamiento indefinido en C ++, consulte la respuesta de @aix.
David Rodríguez - dribeas
@ DavidRodríguez-dribeas ¿Quisiste decir sin especificar , no sin definir , verdad?
dasblinkenlight
4
@dasblinkenlight: No, quiso decir indefinido . En C ++ 03 y anteriores, la modificación del resultado lvalue de una expresión se comporta de manera impredecible en todos los compiladores debido a la falta de un punto de secuencia intermedio. Si no estuviera especificado , se comportaría de manera predecible pero diferente en diferentes compiladores .
Justin ᚅᚔᚈᚄᚒᚔ
2
Hubiera sido útil en una configuración como int f(int &y); f(x += 10);: pasar una referencia a la variable modificada a una función.
Phil Miller
51

Además de ser un código C inválido, la línea

(i+=10)+=10;

daría como resultado un comportamiento indefinido tanto en C como en C ++ 03 porque se modificaría idos veces entre los puntos de secuencia.

En cuanto a por qué está permitido compilar en C ++:

[C ++ N3242 5.17.1] El operador de asignación (=) y los operadores de asignación compuesta se agrupan de derecha a izquierda. Todos requieren un lvalue modificable como su operando izquierdo y devuelven un lvalue que se refiere al operando izquierdo.

El mismo párrafo continúa diciendo que

En todos los casos, la asignación se secuencia después del cálculo del valor de los operandos derecho e izquierdo, y antes del cálculo del valor de la expresión de asignación.

Esto sugiere que en C ++ 11, la expresión ya no tiene un comportamiento indefinido.

NPE
fuente
3
Bueno, ciertamente es UB debido a los puntos de secuencia. También es un código inválido en C (pero no C ++) pero no está relacionado con los puntos de secuencia y debe ser capturado por el compilador.
Konrad Rudolph
2
@KonradRudolph: no, el compilador no está obligado a detectar un comportamiento indefinido, a diferencia del código mal formado. La parte "debería ser capturada por el compilador" es donde no estamos de acuerdo.
2
Los puntos de secuencia no existen en C ++ 11, por lo que la razón real de la UB es que hay dos modificaciones ique no están secuenciadas.
Mankarse
4
Es falso que este sea un comportamiento indefinido. Si la asignación no fuera una secuencia antes del cálculo del valor de la expresión de asignación i = j+=1, el resultado sería un valor indeterminado. En el mismo párrafo, cita "En todos los casos, la asignación se secuencia después del cálculo del valor de los operandos derecho e izquierdo, y antes del cálculo del valor de la expresión de asignación". Por (i+=10)+=10lo tanto, está bien definido para hacer i += 10; i += 10;. Por otro lado (i+=10)+=(i+=10)está UB.
bames53
2
Desde que vi que había un desacuerdo sobre esto entre los comentaristas, publiqué esto como una pregunta separada: stackoverflow.com/questions/10655290/…
NPE