¿Por qué c = ++ (a + b) da error de compilación?

111

Después de investigar, leí que el operador de incremento requiere que el operando tenga un objeto de datos modificable: https://en.wikipedia.org/wiki/Increment_and_decrement_operators .

De esto, supongo que da un error de compilación porque (a+b)es un número entero temporal y, por lo tanto, no es modificable.

¿Es correcto este entendimiento? Esta fue la primera vez que intenté investigar un problema, por lo que si había algo que debería haber buscado, avíseme.

dng
fuente
35
Eso no está mal en términos de investigación. Estás en el camino correcto.
StoryTeller - Unslander Monica
35
¿Qué esperas que haga la expresión?
qrdl
4
de acuerdo con el estándar C11 6.5.3.1: El operando del operador de incremento o decremento de prefijo tendrá un tipo de puntero o real atómico, calificado o no calificado, y será un valor modificable
Christian Gibbons
10
¿Cómo le gustaría que se distribuyera el 1 entre ay b? "¿Deberían los índices de matriz comenzar en 0 o 1? Mi compromiso de 0,5 fue rechazado sin, pensé, la consideración adecuada". - Stan Kelly-Bootle
Andrew Morton
5
Creo que una pregunta de seguimiento es por qué querrías hacer esto cuando c = a + b + 1hace que tu intención sea más clara y también es más corta de escribir. Los operadores de incremento / decremento hacen dos cosas: 1. ellos y su argumento forman una expresión (que se puede usar, por ejemplo, en un bucle for), 2. modifican el argumento. En su ejemplo, está usando la propiedad 1. pero no la propiedad 2., ya que descarta el argumento modificado. Si no necesita la propiedad 2. y solo desea la expresión, puede escribir una expresión, por ejemplo, x + 1 en lugar de x ++.
Trevor

Respuestas:

117

Es solo una regla, eso es todo, y posiblemente esté ahí para (1) facilitar la escritura de compiladores de C y (2) nadie ha convencido al comité de estándares de C para que lo relaje.

Hablando informalmente, solo puede escribir ++foosi foopuede aparecer en el lado izquierdo de una expresión de asignación como foo = bar. Como no puedes escribir a + b = bar, tampoco puedes escribir ++(a + b).

No hay ninguna razón real por la que a + bno pueda producir un temporal en el que ++pueda operar, y el resultado de eso es el valor de la expresión ++(a + b).

Betsabé
fuente
4
Creo que el punto (1) da en el clavo. Solo mirar las reglas para la materialización temporal en C ++ puede revolver el estómago (pero es poderoso, tengo que decirlo).
StoryTeller - Unslander Monica
4
@StoryTeller: De hecho, a diferencia de nuestro amado lenguaje C ++, C todavía se compila en ensamblador de manera relativamente trivial.
Betsabé
29
Aquí hay una razón real en mi humilde opinión: sería una terrible confusión si a ++veces tuviera el efecto secundario de modificar algo y otras veces simplemente no.
aschepler
5
@dng: De hecho lo es; por eso se introdujeron los términos lvalue y rvalue, aunque las cosas son más complicadas que eso hoy en día (particularmente en C ++). Por ejemplo, una constante nunca puede ser un valor l: algo como 5 = a no tiene sentido.
Betsabé
6
@Bathsheba Eso explica por qué 5 ++ también causa un error de compilación
dng
40

El estándar C11 establece en la sección 6.5.3.1

El operando del operador de incremento o decremento de prefijo tendrá un tipo de puntero o real atómico, calificado o no calificado, y será un valor modificable.

Y el "valor modificable" se describe en la sección 6.3.2.1 subsección 1

Un lvalue es una expresión (con un tipo de objeto distinto de void) que potencialmente designa un objeto; si un lvalue no designa un objeto cuando se evalúa, el comportamiento no está definido. Cuando se dice que un objeto tiene un tipo particular, el tipo se especifica mediante el lvalue utilizado para designar el objeto. Un lvalue modificable es un lvalue que no tiene un tipo de matriz, no tiene un tipo incompleto, no tiene un tipo calificado const, y si es una estructura o unión, no tiene ningún miembro (incluyendo, recursivamente, cualquier miembro o elemento de todos los agregados o uniones contenidos) con un tipo calificado const.

Por (a+b)lo tanto, no es un lvalue modificable y, por lo tanto, no es elegible para el operador de incremento de prefijo.

Christian Gibbons
fuente
1
Falta su conclusión de estas definiciones ... Quiere decir que (a + b) no designa potencialmente un objeto, pero estos párrafos no lo permiten.
hkBst
21

Estás en lo correcto. la ++trata de asignar el nuevo valor a la variable original. Entonces ++atomará el valor de a, lo agregará 1y luego lo volverá a asignar a. Como, como dijiste, (a + b) es un valor temporal y no una variable con una dirección de memoria asignada, la asignación no se puede realizar.

Roee Gavirel
fuente
12

Creo que en su mayoría respondiste a tu propia pregunta. Podría hacer un pequeño cambio en su fraseo y reemplazar "variable temporal" con "rvalue" como mencionó C.Gibbons.

Los términos variable, argumento, variable temporal, etc. se volverán más claros a medida que aprenda sobre el modelo de memoria de C (esto parece una buena descripción general: https://www.geeksforgeeks.org/memory-layout-of-c-program/ ).

El término "rvalue" puede parecer opaco cuando recién está comenzando, así que espero que lo siguiente ayude a desarrollar una intuición al respecto.

Lvalue / rvalue se refieren a los diferentes lados de un signo igual (operador de asignación): lvalue = lado izquierdo (L minúscula, no "uno") rvalue = lado derecho

Aprender un poco sobre cómo C usa la memoria (y los registros) será útil para ver por qué la distinción es importante. Con pinceladas amplias , el compilador crea una lista de instrucciones en lenguaje de máquina que calculan el resultado de una expresión (el rvalue) y luego coloca ese resultado en algún lugar (el lvalue). Imagine un compilador que se ocupa del siguiente fragmento de código:

x = y * 3

En el pseudocódigo de ensamblaje, podría parecerse a este ejemplo de juguete:

load register A with the value at memory address y
load register B with a value of 3
multiply register A and B, saving the result in A
write register A to memory address x

El operador ++ (y su - contraparte) necesita un "lugar" para modificar, esencialmente cualquier cosa que pueda funcionar como un lvalue.

Comprender el modelo de memoria C será útil porque obtendrá una mejor idea en su cabeza sobre cómo se pasan los argumentos a las funciones y (eventualmente) cómo trabajar con la asignación de memoria dinámica, como la función malloc (). Por razones similares, podría estudiar alguna programación simple en ensamblador en algún momento para tener una mejor idea de lo que está haciendo el compilador. Además, si está utilizando gcc , la opción -S "Deténgase después de la etapa de compilación propiamente dicha; no ensamble". puede ser interesante (aunque recomiendo probarlo en un pequeño fragmento de código).

Solo como un aparte: la instrucción ++ ha existido desde 1969 (aunque comenzó en el predecesor de C, B):

La observación (de Ken Thompson) (fue) de que la traducción de ++ x era menor que la de x = x + 1 ".

Seguir esa referencia de wikipedia lo llevará a un artículo interesante de Dennis Ritchie (la "R" en "K&R C") sobre la historia del lenguaje C, vinculado aquí para su conveniencia: http://www.bell-labs.com/ usr / dmr / www / chist.html donde puede buscar "++".

jgreve
fuente
6

La razón es que el estándar requiere que el operando sea un valor l. La expresión (a+b)no es un lvalue, por lo que no se permite aplicar el operador de incremento.

Ahora, se podría decir "OK, que es de hecho la razón, pero en realidad no hay * real * razón aparte de eso" , pero por desgracia la formulación particular de cómo funciona el operador de hecho no requerir que ser el caso.

La expresión ++ E es equivalente a (E + = 1).

Obviamente, no puede escribir E += 1si Eno es un lvalue. Lo cual es una lástima porque también se podría haber dicho: "incrementa E en uno" y listo. En ese caso, aplicar el operador en un valor distinto de l sería (en principio) perfectamente posible, a costa de hacer que el compilador sea un poco más complejo.

Ahora, la definición podría reformularse trivialmente (creo que ni siquiera es originalmente C, sino una reliquia de B), pero hacerlo cambiaría fundamentalmente el lenguaje a algo que ya no es compatible con sus versiones anteriores. Dado que el posible beneficio es bastante pequeño pero las posibles implicaciones son enormes, eso nunca sucedió y probablemente nunca sucederá.

Si considera C ++ además de C (la pregunta está etiquetada como C, pero hubo una discusión sobre las sobrecargas de operadores), la historia se vuelve aún más complicada. En C, es difícil imaginar que este podría ser el caso, pero en C ++ el resultado de (a+b)podría muy bien ser algo que no se puede incrementar en absoluto, o incrementar podría tener efectos secundarios muy considerables (no solo agregar 1). El compilador debe poder hacer frente a eso y diagnosticar los casos problemáticos a medida que ocurren. En un valor, todavía es un poco trivial comprobarlo. No es así para cualquier tipo de expresión fortuita entre paréntesis que le arrojes al pobre.
Esta no es una razón real por la que no podría Se puede hacer, pero sin duda sirve como explicación de por qué las personas que implementaron esto no están precisamente encantadas de agregar una característica que promete muy pocos beneficios a muy pocas personas.

Damon
fuente
3

(a + b) evalúa a un rvalue, que no se puede incrementar.

Casper B. Hansen
fuente
3

++ intenta dar el valor a la variable original y dado que (a + b) es un valor temporal, no puede realizar la operación. Y son básicamente reglas de las convenciones de programación de C para facilitar la programación. Eso es.

Babu Chandermani
fuente
2

Cuando se realiza la expresión ++ (a + b), entonces, por ejemplo:

int a, b;
a = 10;
b = 20;
/* NOTE :
 //step 1: expression need to solve first to perform ++ operation over operand
   ++ ( exp );
// in your case 
   ++ ( 10 + 20 );
// step 2: result of that inc by one 
   ++ ( 30 );
// here, you're applying ++ operator over constant value and it's invalid use of ++ operator 
*/
++(a+b);
Jeet Parikh
fuente