Comportamiento de las sentencias arr[i]=i++ y i=i+1 en C y C++
c++14 undefined-behavior (5)
En su ejemplo, una [i] = i ++, si i = 3 por ejemplo, ¿cree que primero se evalúa una [i] o i ++? En un caso, el valor 3 se almacenaría en un [3], en el otro caso, se almacenaría en un [4]. Es obvio que tenemos un problema aquí. Ninguna persona sensata se atrevería a escribir ese código a menos que encuentre una garantía de lo que sucederá aquí. (Java da esa garantía).
¿Qué crees que podría ser un problema con i = i + 1? El idioma debe leer i primero para calcular i + 1, luego almacenar ese resultado. No hay nada aquí que pueda estar equivocado. Lo mismo con una [i] = i + 1. Evaluar i + 1, a diferencia de i ++, no cambia i. Entonces, si i = 3, el número 4 debe almacenarse en un [3].
Varios idiomas tienen varias reglas para solucionar el problema con [i] = i ++. Java define lo que sucede: las expresiones se evalúan de izquierda a derecha, incluidos sus efectos secundarios. C lo define como un comportamiento indefinido. C ++ no lo convierte en un comportamiento indefinido , sino que simplemente no se especifica. Dice que primero se evalúa a [i] o i ++, y luego al otro, pero no dice cuál. Así que, a diferencia de C, donde todo puede suceder, C ++ define que solo una de dos cosas puede suceder. Obviamente, eso es una cosa demasiado para ser aceptable en su código.
En los lenguajes C y C ++, arr[i] = i++;
declaración invoca un comportamiento indefinido. ¿Por qué la declaración i = i + 1;
¿No invocar un comportamiento indefinido?
Para C99, tenemos:
6.5 Expresiones
- Entre el punto de secuencia anterior y el siguiente, un objeto tendrá su valor almacenado modificado a lo sumo una vez por la evaluación de una expresión. Además, el valor anterior se leerá solo para determinar el valor que se almacenará.
En arr[i] = i++
, el valor de i
solo se modifica una vez. Pero arr[i]
también lee desde i
, y este valor no se usa para determinar el nuevo valor de i
. Por eso tiene un comportamiento indefinido.
Por otro lado, en i = i + 1
leemos i
para calcular i + 1
, que se utiliza como el nuevo valor de i
. Por lo tanto esta expresión está bien.
Tenga en cuenta que esto cambiará en C ++ 17. En C ++ 17, arr[i] = i++
no invoca un comportamiento indefinido. Esto se debe al siguiente cambio en [expr.ass] :
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. El operando derecho está secuenciado antes que el operando izquierdo.
Es decir, hacemos i++
luego arr[i]
luego realizamos la tarea. El ordenamiento ahora bien definido es:
auto src = i++;
auto& dst = arr[i];
dst = src;
Dado que originalmente se etiquetó con c y c ++ y no con ninguna versión específica, la siguiente respuesta es una respuesta genérica al problema. Sin embargo, tenga en cuenta que para c ++ , C++17
adelante, el comportamiento ha cambiado. Por favor, vea esta respuesta por Barry para saber más.
Por la declaración
arr[i] = i++;
el valor de i
se usa en los operandos, RHS (lado derecho) y LHS (lado izquierdo), y en uno de los casos, el valor se está modificando (como un efecto secundario de post ++
) donde no hay un punto de secuencia en el medio para determinar qué valor de i
debe considerarse. También puede consultar esta respuesta canónica para más información sobre esto.
Por otro lado, para i = i + 1
, el valor de i
se usa solo en RHS, el resultado calculado se almacena en LHS, en otras palabras, no hay ambigüedad. Podemos escribir la misma declaración que i++
, que
- lee el valor de
i
- Se incrementa en
1
- lo almacena de nuevo a
i
en una secuencia bien definida. Por lo tanto, no hay problemas.
arr[i] = i++;
implica que
- la expresión de la mano derecha se evalúa antes de la asignación
- El operador del subíndice se evalúa antes de la asignación
pero contiene ambigüedad con respecto al orden de la evaluación de la expresión de la mano derecha y la evaluación del operador del subíndice, el compilador es libre de tratarlo como
auto & val{arr[i]};
i++;
auto const rval{i};
val = rval;
o como
i++;
auto & val{arr[i]};
auto const rval{i};
val = rval;
o como (el mismo resultado que arriba)
i++;
auto const rval{i};
auto & val{arr[i]};
val = rval;
Lo que puede producir resultados impredecibles, mientras que
i = i + 1;
Dos no tienen ninguna ambigüedad, la expresión de la mano derecha se evalúa antes de la asignación:
auto const rval{i + 1};
auto & val{i};
val = rval;
o (mismo resultado que el anterior)
auto & val{i};
auto const rval{i + 1};
val = rval;