c++ undefined-behavior c++-faq sequence-points

c++ - Comportamiento indefinido y puntos de secuencia.



undefined-behavior c++-faq (5)

¿Qué son los "puntos de secuencia"?

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

A menudo utilizo 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: se pretende que sea una entrada a las Preguntas frecuentes sobre C ++ de Stack Overflow . Si desea criticar la idea de proporcionar una Pregunta frecuente en este formulario, la publicación en el meta que inició todo esto sería el lugar para hacerlo. esa pregunta se monitorea en la sala de chat de C ++ , donde comenzó la idea de las preguntas frecuentes en primer lugar, por lo que es muy probable que su respuesta sea leída por aquellos a quienes se les ocurrió la idea.)


C ++ 98 y C ++ 03

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

Descargo de responsabilidad : está bien. 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.

Pre-requisitos : Un conocimiento elemental de C ++ Standard

¿Qué son los puntos de secuencia?

La norma dice

En ciertos puntos especificados de la secuencia de ejecución llamados puntos de secuencia , todos los efectos secundarios de las evaluaciones previas deberán estar completos y no se habrán producido efectos secundarios de las evaluaciones posteriores. (§1.9 / 7)

¿Efectos secundarios? ¿Qué 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 algunos efectos secundarios.

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 del operador ++ .

Hasta ahora tan bueno. Pasando a los puntos de secuencia. Una definición alternativa de los puntos de secuencia dada por el autor de comp.lang.c Steve Summit :

El punto de secuencia es un punto en el tiempo en el que el polvo se ha asentado y se garantiza que todos los efectos secundarios que se han visto hasta ahora están completos.

¿Cuáles son los puntos de secuencia comunes listados 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; en func(a,a++) , no es un operador de coma, es simplemente un separador entre los argumentos a y 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 (ya sea que la función esté o no en línea), después de la evaluación de todos los argumentos de la función (si corresponde) 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 forman parte léxica de la expresión completa. Por ejemplo, las subexpresiones involucradas en la evaluación de las expresiones de argumento predeterminadas (8.3.6) se consideran creadas en la expresión que llama a la función, no en la expresión que define el argumento predeterminado

2: los operadores indicados son operadores integrados, 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 el comportamiento indefinido?

La norma define el comportamiento indefinido en la sección §1.3.12 como

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

También puede esperarse un comportamiento indefinido cuando esta Norma Internacional omita la descripción de cualquier definición explícita de comportamiento.

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

En resumen, un comportamiento indefinido significa que cualquier cosa puede pasar desde demonios que vuelan por la nariz hasta que su novia se 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 entre comportamiento indefinido, comportamiento no especificado y comportamiento definido por la implementación .

También debe 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 here .

Ahora la norma en §5/4 dice

  • 1) Entre el punto de secuencia anterior y siguiente, un objeto escalar tendrá su valor almacenado modificado a lo sumo 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 suele estar en el punto y coma final, y el previous sequence point está al final de la instrucción anterior. Una expresión también puede contener sequence points intermedios.

De la oración anterior, las siguientes expresiones invocan un comportamiento indefinido:

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, solo se debe acceder al valor anterior para determinar el valor que se almacenará.

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

Por ejemplo, en i = i + 1 todos los accesos de i (en LHS y en RHS) están directamente involucrados en el cálculo del valor a escribir. Así que está bien.

Esta regla restringe efectivamente las expresiones legales a aquellas en las que los accesos preceden demostrablemente 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á deshabilitado porque uno de los accesos de i (el que está en a[i] ) no tiene nada que ver con el valor que termina siendo almacenado en i (que sucede en i++ ), por lo que no hay una buena manera de definir-- Para nuestro entendimiento o para el compilador: si el acceso debe tener lugar antes o después de que se almacene el valor incrementado. Entonces el comportamiento es indefinido.

Ejemplo 3:

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

Seguimiento de respuesta para C ++ 11 here .


En C99(ISO/IEC 9899:TC3) que parece estar ausente en esta discusión hasta el momento, se hacen los siguientes comentarios con respecto al orden de evaluación.

[...] el orden de evaluación de las subexpresiones y el orden en que se producen los efectos secundarios no están especificados. (Sección 6.5 pp 67)

El orden de evaluación de los operandos no está especificado. Si se intenta modificar el resultado de un operador de asignación o acceder a él después del siguiente punto de secuencia, el comportamiento [sic] no está definido (Sección 6.5.16 pp 91).


Supongo que hay una razón fundamental para el cambio, no es meramente cosmético aclarar la interpretación anterior: esa razón es concurrencia. Un orden de elaboración no especificado es simplemente la selección de uno de los varios pedidos en serie posibles, esto es muy diferente de antes y después de los pedidos, porque si no hay un pedido específico, la evaluación concurrente es posible: no es así con las reglas antiguas. Por ejemplo en:

f (a,b)

previamente o bien a continuación b, o, b entonces a. Ahora, a y b pueden evaluarse con instrucciones intercaladas o incluso en diferentes núcleos.


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

En particular, se añadió la siguiente frase :

8.18 Operadores de asignación y asignación compuesta :
....

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.

Hace válidos varios casos de comportamiento previamente indefinido, 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, usar un compilador compatible con C ++ 17 no significa necesariamente que uno deba comenzar a escribir tales expresiones.


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

Prerrequisitos : Un conocimiento elemental de las relaciones (Matemáticas).

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

¡Sí! Esto es muy cierto.

Los puntos de secuencia han sido reemplazados por relaciones secuenciadas antes y secuenciadas después (y sin secuencia e indeterminadamente secuenciadas ) en C ++ 11.

¿Qué es exactamente esta cosa ''secuenciada antes''?

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

entre evaluaciones ejecutadas por un solo thread e induce un orden parcial estricto 1

Formalmente significa que, dadas dos evaluaciones (ver a continuación) A y B , si A está secuenciada antes de B , entonces la ejecución de A debe preceder a la ejecución de B Si A no está secuenciada antes de B y B no está secuenciada antes de A , entonces A y B tienen secuencia 2 .

Las evaluaciones A y B se secuencian de forma indeterminada cuando A se secuencia antes de B o B se secuencia antes de A , pero no se especifica cual 3 .

[NOTAS]
1: un orden parcial estricto es una relación binaria "<" sobre un conjunto P que es Asymmetric y Transitive , es decir, para todos a , b y c en P , tenemos que:
........(yo). si a <b entonces ¬ (b <a) ( asymmetry );
........ (ii). si a <b y b <c entonces a <c ( transitivity ).
2: La ejecución de evaluaciones sin secuencia puede superponerse .
3: Las evaluaciones secuenciadas de forma indeterminada no pueden superponerse , pero cualquiera de las dos 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 subexpresión) en general incluye:

Ahora (§1.9 / 14) dice:

Cada cálculo de valor y efecto secundario asociado con una expresión completa se secuencia antes de cada cálculo de valor y efecto secundario asociado con la siguiente expresión completa a evaluar .

  • Ejemplo trivial:

    int x; x = 10; ++x;

    El cálculo del valor y el efecto secundario asociado 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 anteriormente, ¿verdad?

¡Sí! Derecha.

En (§1.9 / 15) se ha mencionado que

Excepto donde se indique, las evaluaciones de los operandos de los operadores individuales y de las subexpresiones de las expresiones individuales no tienen secuencia 4 .

Por ejemplo :

int main() { int num = 19 ; num = (num << 3) + (num >> 3); }

  1. La evaluación de los operandos del operador + tiene secuencia relativa entre sí.
  2. La evaluación de los operandos de los operadores << y >> tienen secuencia relativa entre sí.

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

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

Eso significa que, en x + y el cálculo del valor de x e y se secuencia antes del cálculo del valor de (x + y) .

Más importante

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

(a) otro efecto secundario en el mismo objeto escalar

o

(b) un cálculo de valor utilizando 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)

Al llamar a una función (ya sea que la función esté o no en línea), cada cálculo de valor y efecto secundario asociado con cualquier expresión de argumento, o con la expresión de posfijo 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álculos de valor y los efectos secundarios asociados con diferentes expresiones de argumentos no tienen secuencia . - nota final ]

Las expresiones (5) , (7) y (8) no invocan un comportamiento indefinido. Echa un vistazo a las siguientes respuestas para una explicación más detallada.

Nota final :

Si encuentra algún defecto en la publicación, por favor deje un comentario. Usuarios avanzados (con rep> 20000) no dude en editar la publicación para corregir errores tipográficos y otros errores.