¿Qué significa i = (i, ++ i, 1) + 1; ¿hacer?

174

Después de leer esta respuesta sobre el comportamiento indefinido y los puntos de secuencia, escribí un pequeño programa:

#include <stdio.h>

int main(void) {
  int i = 5;
  i = (i, ++i, 1) + 1;
  printf("%d\n", i);
  return 0;
}

La salida es 2. ¡Oh Dios, no vi venir el decremento! ¿Que está sucediendo aquí?

Además, mientras compilaba el código anterior, recibí una advertencia que decía:

px.c: 5: 8: advertencia: el operando de la izquierda de la expresión de coma no tiene efecto

  [-Wunused-value]   i = (i, ++i, 1) + 1;
                        ^

¿Por qué? Pero probablemente será respondido automáticamente por la respuesta de mi primera pregunta.

gsamaras
fuente
289
No hagas cosas raras, no tendrás amigos :(
Maroun
9
El mensaje de advertencia es la respuesta a su primera pregunta.
Yu Hao
2
@gsamaras: no. el valor resultante se descarta, no la modificación. La respuesta real: el operador de coma crea un punto de secuencia.
Karoly Horvath
3
@gsamaras No debería preocuparte cuando tienes un puntaje positivo y aún más con más de 10 preguntas
LyingOnTheSky
9
Nota: Un compilador de optimización puede simplemente hacerprintf("2\n");
chux - Reinstalar Monica

Respuestas:

256

En la expresión (i, ++i, 1), la coma utilizada es el operador de coma

el operador de coma (representado por el token ,) es un operador binario que evalúa su primer operando y descarta el resultado, y luego evalúa el segundo operando y devuelve este valor (y tipo).

Debido a que descarta su primer operando, generalmente solo es útil cuando el primer operando tiene efectos secundarios deseables . Si el efecto secundario para el primer operando no tiene lugar, entonces el compilador puede generar advertencia sobre la expresión sin efecto.

Entonces, en la expresión anterior, ise evaluará el extremo izquierdo y se descartará su valor. Luego ++iserá evaluado e incrementará ien 1 y nuevamente el valor de la expresión ++iserá descartado, pero el efecto secundario ies permanente . Luego 1será evaluado y el valor de la expresión será 1.

Es equivalente a

i;          // Evaluate i and discard its value. This has no effect.
++i;        // Evaluate i and increment it by 1 and discard the value of expression ++i
i = 1 + 1;  

Tenga en cuenta que la expresión anterior es perfectamente válida y no invoca un comportamiento indefinido porque hay un punto de secuencia entre la evaluación de los operandos izquierdo y derecho del operador de coma.

hacks
fuente
1
aunque la expresión final es válida, ¿no es la segunda expresión ++ i un comportamiento indefinido? se evalúa y el valor de la variable no inicializada se incrementa previamente, ¿cuál no es el correcto? ¿O me estoy perdiendo algo?
Koushik Shetty
2
@Koushik; ise inicia con 5. Mira la declaración de la declaración int i = 5;.
piratea el
1
Oh mi error. Lo siento, sinceramente, no veo eso.
Koushik Shetty
Aquí hay un error: ++ lo aumentaré y luego lo evaluaré, mientras que i ++ evaluará y luego lo incrementaré.
Quentin Hayot
1
@QuentinHayot; ¿Qué? Cualquier efecto secundario tiene lugar después de la evaluación de la expresión. En caso de ++i, esta expresión se evaluará, ise incrementará y este valor incrementado será el valor de la expresión. En caso de i++que se evalúe esta expresión, el valor anterior de iserá el valor de la expresión, ise incrementará en cualquier momento entre el punto de secuencia anterior y el siguiente de la expresión.
Hackea
62

Citando de C11, capítulo 6.5.17, operador de coma

El operando izquierdo de un operador de coma se evalúa como una expresión vacía; Hay un punto de secuencia entre su evaluación y la del operando correcto. Luego se evalúa el operando correcto; El resultado tiene su tipo y valor.

Entonces, en tu caso,

(i, ++i, 1)

se evalúa como

  1. i, se evalúa como una expresión vacía, valor descartado
  2. ++i, se evalúa como una expresión vacía, valor descartado
  3. finalmente, 1valor devuelto.

Entonces, la declaración final parece

i = 1 + 1;

y illega a 2. Supongo que esto responde a sus dos preguntas,

  • ¿Cómo se iobtiene un valor 2?
  • ¿Por qué hay un mensaje de advertencia?

Nota: FWIW, como hay un punto de secuencia presente después de la evaluación del operando de la izquierda, una expresión como (i, ++i, 1)no invocará a UB, como generalmente se puede pensar por error.

Sourav Ghosh
fuente
+1 Sourav, ya que esto explica por qué la inicialización de iclaramente no tiene ningún efecto. Sin embargo, no creo que fuera tan obvio para un tipo que no conoce el operador de coma (y no sabía cómo buscar ayuda, aparte de hacer una pregunta). Lástima que tengo tantos votos negativos! Comprobaré las otras respuestas y luego decidiré cuál aceptar. ¡Gracias! Buena respuesta superior por cierto.
gsamaras
Siento que tengo que explicar por qué acepté la respuesta de los hacks. Estaba listo para aceptar el tuyo, ya que realmente responde mis dos preguntas. Sin embargo, si revisa los comentarios de mi pregunta, verá que algunas personas no pueden ver a primera vista por qué esto no invoca a UB. las respuestas de los hacks proporcionan información relevante. Por supuesto, tengo la respuesta con respecto a UB vinculada en mi pregunta, pero algunas personas pueden perderse eso. Espero que estés de acuerdo con mi decisión, si no, házmelo saber. :)
gsamaras
30
i = (i, ++i, 1) + 1;

Analicémoslo paso a paso.

(i,   // is evaluated but ignored, there are other expressions after comma
++i,  // i is updated but the resulting value is ignored too
1)    // this value is finally used
+ 1   // 1 is added to the previous value 1

Entonces obtenemos 2. Y la asignación final ahora:

i = 2;

Lo que sea que haya en yo antes se sobrescribe ahora.

matraz
fuente
Sería bueno decir que esto sucede debido al operador de coma. ¡+1 para el análisis paso a paso! Buena respuesta superior por cierto.
gsamaras
Lamento la explicación insuficiente, solo tengo una nota allí ( ... pero ignorado, hay ... ). Quería explicar principalmente por qué ++ino contribuye al resultado.
Dlask
ahora mi for loops siempre será comoint i = 0; for( ;(++i, i<max); )
CoffeDeveloper
19

El resultado de

(i, ++i, 1)

es

1

por

(i,++i,1) 

la evaluación ocurre de tal manera que el ,operador descarta el valor evaluado y retendrá el valor más correcto que es1

Entonces

i = 1 + 1 = 2
Gopi
fuente
1
Sí, yo también pensé en eso, ¡pero no sé por qué!
gsamaras
@gsamaras porque el operador de coma evalúa el término anterior pero lo descarta (es decir, no lo usa para tareas o similares)
Marco A.
14

Encontrarás una buena lectura en la página wiki para el operador de coma .

Básicamente

... evalúa su primer operando y descarta el resultado, y luego evalúa el segundo operando y devuelve este valor (y tipo).

Esto significa que

(i, i++, 1)

a su vez, evaluará i, descartará el resultado, evaluará i++, descartará el resultado y luego evaluará y regresará 1.

Tomás Aschan
fuente
O_O demonios, ¿esa sintaxis es válida en C ++? Recuerdo que tenía pocos lugares donde necesitaba esa sintaxis (básicamente escribí: (void)exp; a= exp2;mientras solo necesitaba a = exp, exp2;)
CoffeDeveloper
13

Necesita saber qué está haciendo el operador de coma aquí:

Tu expresión:

(i, ++i, 1)

La primera expresión, ise evalúa, la segunda expresión, ++ise evalúa y la tercera expresión 1, se devuelve para toda la expresión.

Así, el resultado es: i = 1 + 1.

Para su pregunta adicional, como puede ver, la primera expresión ino tiene ningún efecto, por lo que el compilador se queja.

songyuanyao
fuente
5

La coma tiene una precedencia "inversa". Esto es lo que obtendrá de libros antiguos y manuales en C de IBM (70s / 80s). Entonces, el último 'comando' es lo que se usa en la expresión principal.

En la C moderna, su uso es extraño pero es muy interesante en la antigua C (ANSI):

do { 
    /* bla bla bla, consider conditional flow with several continue's */
} while ( prepAnything(), doSomethingElse(), logic_operation);

Si bien todas las operaciones (funciones) se invocan de izquierda a derecha, solo la última expresión se usará como resultado a 'while' condicional. Esto evita el manejo de 'goto's' para mantener un bloque único de comandos para ejecutar antes de la verificación de condición.

EDITAR: Esto evita también una llamada a una función de manejo que podría ocuparse de toda la lógica en los operandos izquierdos y, por lo tanto, devolver el resultado lógico. Recuerde que no teníamos una función en línea en el pasado de C. Por lo tanto, esto podría evitar una sobrecarga de llamadas.

Luciano
fuente
Luciano, también tienes un enlace a esta respuesta: stackoverflow.com/questions/17902992/… .
gsamaras
A principios de los 90 antes de las funciones en línea, lo usé mucho para optimizar y mantener el código organizado.
Luciano