Pregunta Comportamiento indefinido y puntos de secuencia


¿Qué son "puntos de secuencia"?

¿Cuál es la relación entre el comportamiento indefinido y los puntos de secuencia?

A menudo uso expresiones divertidas y complicadas como a[++i] = i;, para hacerme sentir mejor. ¿Por qué debería dejar de usarlos?

Si ha leído esto, asegúrese de visitar la pregunta de seguimiento Comportamiento indefinido y puntos de secuencia recargados.

(Nota: Esto está destinado a ser una entrada a Preguntas frecuentes sobre C ++ de Stack Overflow. Si desea criticar la idea de proporcionar una pregunta frecuente en este formulario, entonces la publicación en meta que comenzó todo esto sería el lugar para hacer eso. Las respuestas a esa pregunta son monitoreadas en Sala de chat C ++, donde la idea de las preguntas frecuentes comenzó en primer lugar, por lo que es muy probable que su respuesta sea leída por aquellos a quienes se les ocurrió la idea).


897


origen


Respuestas:


C ++ 98 y C ++ 03

Esta respuesta es para las versiones anteriores del estándar C ++. Las versiones C ++ 11 y C ++ 14 del estándar no contienen formalmente 'puntos de secuencia'; las operaciones son 'secuenciadas antes' o 'no secuenciadas' o 'secuenciadas indeterminadas' en su lugar. El efecto neto es esencialmente el mismo, pero la terminología es diferente.


Renuncia : Bueno. Esta respuesta es un poco larga. Así que ten paciencia mientras lo lees. Si ya sabes estas cosas, leerlas de nuevo no te volverá loco.

Requisitos previos : Un conocimiento elemental de Estándar C ++ 


¿Qué son los puntos de secuencia?

El estándar dice

En ciertos puntos especificados en la secuencia de ejecución llamada puntos de secuencia, todas efectos secundarios de evaluaciones previas   estará completo y no efectos secundarios evaluaciones posteriores se habrán llevado a cabo. (§1.9 / 7)

¿Efectos secundarios? ¿Cuáles son los efectos secundarios?

La evaluación de una expresión produce algo y, si además hay un cambio en el estado del entorno de ejecución, se dice que la expresión (su evaluación) tiene algún efecto secundario.

Por ejemplo:

int x = y++; //where y is also an int

Además de la operación de inicialización, el valor de y se cambia debido al efecto secundario de ++ operador.

Hasta aquí todo bien. Pasando a los puntos de secuencia. Una definición de alternancia de puntos seq dada por el autor comp.lang.c Steve Summit:

El punto de secuencia es un punto en el tiempo en el cual el polvo se asentó y todos los efectos secundarios que se han visto hasta el momento están garantizados para completarse.


¿Cuáles son los puntos de secuencia comunes enumerados en el estándar de C ++?

Esos son:

  • al final de la evaluación de la expresión completa (§1.9/16) (Una expresión completa es una expresión que no es una subexpresión de otra expresión).1

Ejemplo:

int a = 5; // ; is a sequence point here
  • en la evaluación de cada una de las siguientes expresiones después de la evaluación de la primera expresión (§1.9/18) 2

    • a && b (§5.14) 
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18) (aquí a, b es un operador de coma; func(a,a++)  , no es un operador de coma, es simplemente un separador entre los argumentos ay a++. Por lo tanto, el comportamiento no está definido en ese caso (si a se considera que es un tipo primitivo))
  • en una llamada de función (si la función está en línea o no), después de la evaluación de todos los argumentos de función (si los hay) que tiene lugar antes de la ejecución de cualquier expresión o declaración en el cuerpo de la función (§1.9/17)

1: Nota: la evaluación de una expresión completa puede incluir la evaluación de subexpresiones que no son léxicas parte de la expresión completa. Por ejemplo, las subexpresiones involucradas en la evaluación de expresiones de argumentos por defecto (8.3.6) se consideran creadas en la expresión que llama a la función, no la expresión que define el argumento predeterminado

2: Los operadores indicados son los operadores incorporados, como se describe en la cláusula 5. Cuando uno de estos operadores está sobrecargado (cláusula 13) en un contexto válido, designando así una función de operador definida por el usuario, la expresión designa una invocación de función y los operandos forman una lista de argumentos, sin un punto de secuencia implícito entre ellos.


¿Qué es un comportamiento indefinido?

El estándar define el comportamiento indefinido en la sección §1.3.12 como

comportamiento, tal como podría surgir al usar una construcción de programa errónea o datos erróneos, para los cuales esta Norma Internacional impone sin requisitos 3.

También se puede esperar un comportamiento indefinido cuando esto   Norma Internacional omite la descripción de cualquier definición explícita de comportamiento.

 3: el comportamiento indefinido permitido va desde ignorar completamente la situación con resultados impredecibles, hasta comportarse durante la traducción o la ejecución del programa de una manera documentada y característica del entorno (con o sin fuera de la emisión de un mensaje de diagnóstico), a la terminación de una traducción o ejecución (con la emisión de un mensaje de diagnóstico).

En resumen, un comportamiento indefinido significa cualquier cosa puede pasar de los demonios que salen volando de tu nariz a tu novia que queda embarazada.


¿Cuál es la relación entre el Comportamiento Indefinido y los Puntos de Secuencia?

Antes de entrar en eso, debes saber la diferencia (s) entre Comportamiento indefinido, comportamiento no especificado y comportamiento definido de implementación.

También debes saber que the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

Por ejemplo:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

Otro ejemplo aquí.


Ahora el estándar en §5/4 dice

  • 1) Entre el punto de secuencia anterior y siguiente, un objeto escalar tendrá su valor almacenado modificado como máximo una vez por la evaluación de una expresión. 

Qué significa eso?

Informalmente significa que entre dos puntos de secuencia una variable no debe modificarse más de una vez. En una declaración de expresión, el next sequence point generalmente está en el punto y coma de terminación, y el previous sequence point está al final de la declaración anterior. Una expresión también puede contener intermedios sequence points.

A partir de la oración anterior, las siguientes expresiones invocan Comportamiento no definido:

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

Pero las siguientes expresiones están bien:

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined

  • 2) Además, se accederá al valor anterior solo para determinar el valor que se almacenará.

Qué significa eso? Significa si un objeto se escribe dentro de una expresión completa, cualquiera y todos los accesos a ella dentro de la misma expresión debe estar directamente involucrado en el cálculo del valor a escribir.

Por ejemplo en i = i + 1 todo el acceso de i (en L.H.S y en R.H.S) son directamente involucrado en el cálculo del valor a escribir. Entonces está bien.

Esta regla efectivamente limita las expresiones legales a aquellas en las que los accesos preestablecen la modificación.

Ejemplo 1:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

Ejemplo 2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

está prohibido porque uno de los accesos de i (el de a[i]) no tiene nada que ver con el valor que termina almacenado en i (que sucede en i++), por lo que no hay una buena manera de definir, ya sea para nuestro entendimiento o para el del compilador, si el acceso debe tener lugar antes o después de que se almacene el valor incrementado. Entonces el comportamiento no está definido.

Ejemplo 3:

int x = i + i++ ;// Similar to above

Respuesta de seguimiento aquí. 


623



Este es un seguimiento de mi respuesta anterior y contiene material relacionado con C ++ 11..


Requisitos previos : Un conocimiento elemental de Relaciones (Matemáticas).


¿Es cierto que no hay puntos de secuencia en C ++ 11?

¡Sí! Esto es muy cierto.

Puntos de secuencia han sido reemplazados por Secuenciado antesy Secuencia después (y Sin seguimientoy Indeterminado secuenciado) relaciones en C ++ 11.


¿Qué es exactamente esto 'Secuenciado antes'?

Secuenciado antes(§1.9 / 13) es una relación que es:

entre evaluaciones ejecutadas por un solo hilo e induce una orden parcial estricta1

Formalmente significa dadas dos evaluaciones(Vea abajo)  Ay B, Si A es secuenciado antes  B, luego la ejecución de A  deberá preceder la ejecución de B. Si A no está secuenciado antes By B no está secuenciado antes A, entonces Ay B son no secuenciado  2.

Evaluaciones Ay B son indeterminado secuenciado cuando cualquiera A está secuenciado antes B o B está secuenciado antes A, pero no se especifica qué3.

[NOTAS]
  1: una orden parcial estricta es una relación binaria  "<" sobre un conjunto P cual es asymmetricy transitive, es decir, para todos a, by c en P, tenemos eso:
 
  ........(yo). si a <b luego ¬ (b <a) (asymmetry);
  ........ (ii). si a <b y b <c entonces a <c (transitivity)
  2: la ejecución de evaluaciones no secuenciadas poder superposición.
  3: Evaluaciones secuenciadas indeterminadamente no poder superposición, pero cualquiera podría ejecutarse primero.


 ¿Cuál es el significado de la palabra 'evaluación' en el contexto de C ++ 11?

En C ++ 11, la evaluación de una expresión (o una sub-expresión) en general incluye:

  • cómputos de valor (incluida la determinación de la identidad de un objeto para evaluación de glvalue y recuperar un valor previamente asignado a un objeto para evaluación prvalue) y

  • iniciación de efectos secundarios.

Ahora (§1.9 / 14) dice:

Cada cómputo de valor y efecto secundario asociado con una expresión completa es secuenciado antes cada cómputo de valor y efecto secundario asociado con el próxima expresión completa para ser evaluada.

  • Ejemplo trivial:

    int x; x = 10; ++x;

    Cómputo de valor y efectos secundarios asociados con ++x se secuencia después del cálculo del valor y el efecto secundario de x = 10; 


Entonces debe haber alguna relación entre el Comportamiento Indefinido y las cosas mencionadas, ¿verdad?

¡Sí! Derecha.

En (§1.9 / 15) se ha mencionado que

Salvo que se indique lo contrario, las evaluaciones de operandos de operadores individuales y de subexpresiones de expresiones individuales son no secuenciado4.

Por ejemplo :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Evaluación de operandos de + operador no son secuenciados entre sí.
  2. Evaluación de operandos de <<y >> los operadores no son secuenciados entre sí.

 4: en una expresión que se evalúa más de una vez durante la ejecución de un programa, no secuenciadoy indeterminado secuenciado las evaluaciones de sus subexpresiones no necesitan realizarse de manera consistente en diferentes evaluaciones.

(§1.9 / 15)   Los cálculos de valor de los operandos de un   operador se secuencian antes del cálculo del valor del resultado del operador.

Eso significa en x + y el cálculo del valor de xy y se secuencian antes del cálculo del valor de (x + y).

Más importante

(§1.9 / 15) Si un efecto secundario en un objeto escalar no es secuenciado en relación con cualquiera

(un) otro efecto secundario en el mismo objeto escalar 

o

(segundo) un cálculo de valor usando el valor del mismo objeto escalar.

el comportamiento es indefinido.

Ejemplos:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour 
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

Cuando se llama a una función (esté o no en línea), cada cómputo de valor y efecto secundario asociado con cualquier expresión de argumento, o con la expresión de postfijo que designa la función llamada, se secuencia antes de la ejecución de cada expresión o declaración en el cuerpo del llamada función. [Nota:  Los cómputos de valor y los efectos secundarios asociados con diferentes expresiones de argumento no se han secuenciado. - nota final]

Expresiones (5), (7)y (8) no invocar un comportamiento indefinido Vea las siguientes respuestas para una explicación más detallada.


Nota final :

Si encuentra algún error en la publicación, por favor deje un comentario. Los usuarios avanzados (con representante> 20000) no duden en editar la publicación para corregir errores tipográficos y otros errores.


257



C ++ 17 (N4659) incluye una propuesta Orden de evaluación de expresión de refinación para Idiomatic C ++ que define un orden más estricto de evaluación de expresión.

En particular, el la siguiente frase fue añadido:

8.18 Asignación y operadores de asignación compuesta:
....

En todos los casos, la asignación se secuencia después del valor   cálculo de los operandos derecho e izquierdo, y antes del cálculo del valor de la expresión de asignación.    El operando derecho se secuencia antes del operando izquierdo.

Hace que varios casos de comportamiento previamente indefinido sean válidos, incluido el que está en cuestión:

a[++i] = i;

Sin embargo, varios otros casos similares todavía conducen a un comportamiento indefinido.

En N4140:

i = i++ + 1; // the behavior is undefined

Pero en N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

Por supuesto, el uso de un compilador compatible con C ++ 17 no significa necesariamente que uno deba comenzar a escribir tales expresiones.


13



Supongo que hay una razón fundamental para el cambio, no es meramente cosmético aclarar la antigua interpretación: esa razón es la concurrencia. El orden de elaboración no especificado es meramente la selección de uno de varios posibles pedidos en serie, esto es bastante diferente a los pedidos anteriores y posteriores, porque si no hay un pedido especificado, la evaluación concurrente es posible: no ocurre lo mismo con las reglas anteriores. Por ejemplo en:

f (a,b)

previamente ya sea a entonces b, o, b luego a. Ahora, a y b pueden evaluarse con instrucciones intercaladas o incluso en diferentes núcleos.


11