Su pregunta probablemente no fue, "¿Por qué estas construcciones son comportamientos indefinidos en C?". Probablemente su pregunta fue: "¿Por qué este código (usando ++
) no me dio el valor que esperaba?", Y alguien marcó su pregunta como un duplicado y lo envió aquí.
Esta respuesta intenta responder a esa pregunta: ¿por qué su código no le dio la respuesta que esperaba y cómo puede aprender a reconocer (y evitar) expresiones que no funcionarán como se esperaba?
Supongo que ya ha escuchado la definición básica de C ++
y --
operadores, y cómo el prefijo ++x
difiere del postfix x++
. Pero es difícil pensar en estos operadores, así que para asegurarte de que entiendes, tal vez escribiste un pequeño programa de prueba que involucra algo como
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
Pero, para su sorpresa, este programa no lo ayudó a comprender: imprimió un resultado extraño, inesperado e inexplicable, lo que sugiere que tal vez ++
hace algo completamente diferente, en absoluto lo que pensaba que hizo.
O tal vez estás viendo una expresión difícil de entender como
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
Quizás alguien te dio ese código como un rompecabezas. Este código tampoco tiene sentido, especialmente si lo ejecuta, y si lo compila y ejecuta bajo dos compiladores diferentes, ¡es probable que obtenga dos respuestas diferentes! ¿Que pasa con eso? ¿Qué respuesta es la correcta? (Y la respuesta es que ambos lo son, o ninguno lo es).
Como ya has escuchado, todas estas expresiones son indefinidas , lo que significa que el lenguaje C no garantiza lo que harán. Este es un resultado extraño y sorprendente, porque probablemente pensó que cualquier programa que pudiera escribir, siempre y cuando se compilara y ejecutara, generaría un resultado único y bien definido. Pero en el caso de un comportamiento indefinido, eso no es así.
¿Qué hace que una expresión sea indefinida? ¿Son las expresiones envolventes ++
y --
siempre indefinidas? Por supuesto que no: estos son operadores útiles, y si los usa correctamente, están perfectamente bien definidos.
Para las expresiones de las que estamos hablando, lo que las hace indefinidas es cuando ocurren muchas cosas a la vez, cuando no estamos seguros de en qué orden sucederán las cosas, pero cuando el orden es importante para el resultado que obtenemos.
Volvamos a los dos ejemplos que he usado en esta respuesta. Cuando escribi
printf("%d %d %d\n", x, ++x, x++);
la pregunta es, antes de llamar printf
, ¿el compilador calcula el valor de x
primero, o x++
, o tal vez ++x
? Pero resulta que no sabemos . No hay una regla en C que diga que los argumentos de una función se evalúan de izquierda a derecha, o de derecha a izquierda, o en algún otro orden. Así que no podemos decir si el compilador hará x
primero, y luego ++x
, a continuación x++
, o x++
entonces ++x
a continuación x
, o algún otro fin. Pero el orden claramente importa, porque dependiendo del orden que use el compilador, obtendremos claramente diferentes resultados impresos printf
.
¿Qué hay de esta expresión loca?
x = x++ + ++x;
El problema con esta expresión es que contiene tres intentos diferentes de modificar el valor de x: (1) la x++
parte intenta agregar 1 a x, almacenar el nuevo valor x
y devolver el valor anterior de x
; (2) la ++x
parte intenta agregar 1 a x, almacenar el nuevo valor x
y devolver el nuevo valor de x
; y (3) la x =
parte intenta asignar la suma de los otros dos a x. ¿Cuál de esos tres intentos de tareas "ganará"? ¿A cuál de los tres valores se le asignará realmente x
? De nuevo, y quizás sorprendentemente, no hay una regla en C que nos diga.
Puede imaginar que la precedencia o la asociatividad o la evaluación de izquierda a derecha le dicen en qué orden suceden las cosas, pero no es así. Puede que no me creas, pero por favor toma mi palabra y lo diré de nuevo: la precedencia y la asociatividad no determinan todos los aspectos del orden de evaluación de una expresión en C. En particular, si dentro de una expresión hay múltiples diferentes lugares donde tratamos de asignar un nuevo valor a algo como x
, la precedencia y la asociatividad no nos dicen cuál de esos intentos ocurre primero, o el último, o algo así.
Entonces, con todo ese trasfondo e introducción fuera del camino, si quieres asegurarte de que todos tus programas estén bien definidos, ¿qué expresiones puedes escribir y cuáles no?
Estas expresiones están todas bien:
y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;
Estas expresiones son todas indefinidas:
x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);
Y la última pregunta es, ¿cómo puede saber qué expresiones están bien definidas y qué expresiones no están definidas?
Como dije antes, las expresiones indefinidas son aquellas en las que hay demasiadas cosas a la vez, donde no puedes estar seguro de en qué orden suceden las cosas y dónde importa el orden:
- Si hay una variable que está siendo modificada (asignada) en dos o más lugares diferentes, ¿cómo sabe qué modificación ocurre primero?
- Si hay una variable que se está modificando en un lugar y que su valor se usa en otro lugar, ¿cómo sabe si usa el valor anterior o el nuevo?
Como un ejemplo de # 1, en la expresión
x = x++ + ++x;
Hay tres intentos de modificar `x.
Como un ejemplo de # 2, en la expresión
y = x + x++;
ambos usamos el valor de x
y lo modificamos.
Entonces esa es la respuesta: asegúrese de que en cualquier expresión que escriba, cada variable se modifique como máximo una vez, y si se modifica una variable, no intente usar el valor de esa variable en otro lugar.