c++ c c++14 undefined-behavior sequence-points

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

  1. 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;