una secuenciación secuenciacion secuencia pirosecuenciacion nueva illumina hacer generacion fundamento ejemplos como automatica adn c++ c++11 language-lawyer

c++ - secuenciación - secuenciacion illumina pdf



¿Cómo determinar qué es ''secuenciado'' antes que otros? (2)

Repasé esta excelente respuesta en relación con el comportamiento indefinido y las relaciones secuenciadas [antes / después] en C ++ 11. Entiendo los conceptos de relaciones binarias, pero me estoy perdiendo cuáles son las nuevas reglas que rigen la secuenciación.

Para estos ejemplos familiares, ¿cómo se aplican las nuevas reglas de secuencia?

  1. i = ++i;
  2. a[++i] = i;

Más específicamente, ¿cuáles son las nuevas reglas de secuenciación de C ++ 11?

Estoy buscando algunas reglas como (esta está completamente inventada)

El lhs de una instrucción ''='' siempre se secuencia antes de los rhs , y por lo tanto se evalúa primero.

En caso de que estén disponibles en la norma, ¿puede alguien citar lo mismo aquí?


En mi opinión, esta es una regla mucho más compleja que la antigua regla de los puntos de secuencia, y no soy 100% positivo. Lo entendí bien ... de todos modos, todo se reduce a si para obtener el valor que necesita, el efecto secundario haber sido ya aplicado.

Primer caso

i = ++i;

Aquí, para hacer la asignación, necesita el valor de la parte correcta y para obtener ese valor, necesita que el efecto secundario ya se haya aplicado; por lo tanto, aquí la secuencia se secuencia después del incremento y todo está bien. El punto importante aquí es que para hacer la asignación necesita el valor de RHS y solo la dirección de LHS.

Recordar:

  1. la asignación se secuencia después de &i y ++i
  2. ++i se secuencia después del incremento
  3. La asignación (transitividad) se secuencia después del incremento

El valor de i se lee solo una vez, después del incremento. Se escribe dos veces, una vez por el incremento y una vez por la asignación, pero estas dos operaciones se secuencian (primero el incremento, luego la asignación).

Segundo caso

a[++i] = i;

Aquí, en cambio, necesita el valor de i para RHS y el valor de ++i para LHS. Sin embargo, estas dos expresiones no están secuenciadas (el operador de asignación no impone una secuenciación) y, por lo tanto, el resultado no está definido.

Recordar:

  1. la asignación se secuencia después de &a[++i] y i
  2. &a[++i] se secuencia después de ++i
  3. ++i se secuencia después del incremento

Aquí el valor de i se lee dos veces, una para el LHS de asignación y una para el RHS. La parte LHS también hace una modificación (el incremento). Sin embargo, este acceso de escritura y el acceso de lectura de la asignación RHS no están secuenciados entre sí, y por lo tanto esta expresión es UB.

Despojo final

Permítanme repetir que no estoy seguro de lo que acabo de decir ... mi opinión es que este nuevo enfoque de secuencia antes / después es mucho más difícil de entender. Es de esperar que las nuevas reglas solo hicieran algunas expresiones que antes eran UB bien definidas (y UB es el peor resultado posible), pero también hizo que las reglas fueran mucho más complejas (simplemente "no cambia la misma cosa dos veces entre puntos de secuencia" "... no tenía que hacer un ordenamiento topológico mental para adivinar si algo era UB o no).

En cierto sentido, las nuevas reglas no dañaron los programas de C ++ (UB es el enemigo, y ahora hay menos UB en esta área) pero causó un daño al lenguaje al aumentar la complejidad (y, por supuesto, algo que C ++ no necesitaba era una complejidad añadida). ).

Tenga en cuenta también que lo curioso de ++i es que el valor devuelto es un valor l (por eso ++ ++ i es legal), por lo que es básicamente una dirección y no fue lógicamente necesario que el valor devuelto se secuencia después de el incremento. Pero el estándar lo dice y esta es la regla que necesitas para quemar tus neuronas. Por supuesto, para tener un ++i "utilizable", usted desea que los usuarios del valor obtengan el valor actualizado, pero aún así, en la medida en que el operador ++ ve cosas (está devolviendo una dirección que no se ve afectada por el incremento) esta secuenciación fue No es formalmente necesario.

Con las nuevas reglas, no solo es necesario hacer una clasificación topológica mental para ver si una expresión es válida, sino que también debe hacerlo utilizando relaciones de secuencia arbitrarias que solo necesita memorizar.

Si bien, como programador, es de esperar que nunca escriba código que cambie el mismo valor varias veces sin una secuencia clara, aún así se encontrará con errores en el código escrito por otros programadores ... donde las cosas no son tan claras y donde ahora debe pensar más para comprender si algo es legal C ++ o no.


La relación secuencia-antes , y las reglas que la conciernen son una "ordenación" de las reglas anteriores sobre puntos de secuencia, definidas de manera coherente con las otras relaciones del modelo de memoria, como sucede-antes y se sincroniza, para que pueda ser Especificó con precisión qué operaciones y efectos son visibles en qué circunstancias.

Las consecuencias de las reglas no se modifican para el código simple de un solo hilo.

Comencemos con sus ejemplos:

1. i = ++i;

Si i es un tipo integrado como int entonces no hay llamadas de función involucradas, todo es un operador incorporado. Hay así 4 cosas que pasan:

(a) El cálculo del valor de ++i , que es el valor original de i +1

(b) El efecto secundario de ++i , que almacena el valor original de i +1 nuevamente en i

(c) El cálculo del valor de la asignación, que es solo el valor almacenado, en este caso el resultado del cálculo del valor de ++i

(d) El efecto secundario de la asignación, que almacena el nuevo valor en i

Todas estas cosas se secuencian antes de la siguiente expresión completa. (Es decir, todos están completos al final del punto y coma de la declaración)

Como ++i es equivalente a i+=1 , el efecto secundario de almacenar el valor se secuencia antes del cálculo del valor de ++i , por lo que (b) se secuencia antes de (a).

El cálculo del valor de los dos operandos de una asignación se secuencia antes del cálculo del valor de la propia asignación y, a su vez, se secuencia el efecto secundario de almacenar el valor. Por lo tanto (a) se secuencia antes de (c), y (c) se secuencia antes de (d).

Por lo tanto, tenemos (b) -> (a) -> (c) -> (d), y por lo tanto esto está bien bajo las nuevas reglas, mientras que no estaba bien en C ++ 98.

Si i fuera una class , entonces la expresión sería i.operator=(i.operator++()) , o i.operator=(operator++(i)) , y todos los efectos de la llamada al operator++ se secuencian antes de la llamada al operator= .

2. a[++i] = i;

Si a es un tipo de matriz, e i es un int , entonces nuevamente la expresión tiene varias partes:

(a) El cálculo del valor de i

(b) El cálculo del valor de ++i

(c) El efecto secundario de ++i , que almacena el nuevo valor en i

(d) El cálculo del valor de a[++i] , que devuelve un lvalue para el elemento de a índice por el cálculo del valor de ++i

(e) El cálculo del valor de la asignación, que es solo el valor almacenado, en este caso el resultado del cálculo del valor de i

(f) El efecto secundario de la asignación, que almacena el nuevo valor en el elemento de matriz a[++i]

De nuevo, todas estas cosas se secuencian antes de la siguiente expresión completa. (Es decir, todos están completos al final del punto y coma de la declaración)

Nuevamente, dado que ++i es equivalente a i+=1 , el efecto secundario de almacenar el valor se secuencia antes del cálculo del valor de ++i , por lo que (c) se secuencia antes de (b).

El cálculo del valor del índice de matriz ++i es * secuenciado antes del cálculo del valor de la selección del elemento, por lo que (b) está secuenciado antes (d).

El cálculo del valor de los dos operandos de una asignación se secuencia antes del cálculo del valor de la propia asignación y, a su vez, se secuencia el efecto secundario de almacenar el valor. Por lo tanto (a) y (d) se secuencian antes de (e), y (e) se secuencian antes de (f).

Por lo tanto, tenemos dos secuencias: (a) -> (d) -> (e) -> (f) y (c) -> (b) -> (d) -> (e) -> (f).

Desafortunadamente, no hay orden entre (a) y (c). Por lo tanto, un efecto secundario que almacena en i tiene secuencia con respecto a un cálculo de valor en i , y el código muestra un comportamiento indefinido . Esto es nuevamente dado por 1.9p15 del estándar C ++ 11.

Como antes, si i es de tipo de clase, entonces todo está bien, porque los operadores se convierten en llamadas de función, que imponen la secuenciación.

Las normas

Las reglas son relativamente sencillas:

  1. Los cálculos de valores de los argumentos de un operador incorporado se secuencian antes del cálculo de valores del propio operador.

  2. Los efectos secundarios de un operador de asignación integrado o un operador de preincremento se secuencian antes del cálculo del valor del resultado.

  3. El cálculo del valor de cualquier otro operador incorporado se secuencia antes de los efectos secundarios de ese operador.

  4. El cálculo del valor y los efectos secundarios del lado izquierdo del operador de coma incorporado se secuencian antes del cálculo del valor y los efectos secundarios del lado derecho.

  5. Todos los cálculos de valores y los efectos secundarios de una expresión completa se secuencian antes de la siguiente expresión completa.

  6. El cálculo del valor y los efectos secundarios de los argumentos de una llamada de función se secuencian antes de la primera expresión completa en la función.

  7. El cálculo del valor y los efectos secundarios de todo lo que se encuentra dentro de una función se secuencian antes del cálculo del valor del resultado.

  8. Para cualquiera de las dos llamadas de función en la expresión completa, se secuencia el cálculo del valor del resultado de una , antes de la llamada a la otra, o viceversa. Si ninguna otra regla especifica el orden, el compilador puede elegir.

    Así, en a()+b() , a() se secuencia antes de b() , o b() se secuencia antes de a() , pero no hay una regla para especificar cuál.

  9. Si hay dos efectos secundarios que modifican la misma variable, y ninguno está secuenciado antes que el otro, el código tiene un comportamiento indefinido.

  10. Si hay un efecto secundario que modifica una variable y un cálculo de valor que lee esa variable, y ninguno de ellos está secuenciado antes que el otro, el código tiene un comportamiento indefinido.