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 indeterminadamente' 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 nuevamente no te volverá loco.
Prerrequisitos : un conocimiento elemental de C ++ Standard
¿Qué son los puntos de secuencia?
El estándar dice
En ciertos puntos especificados en la secuencia de ejecución llamados puntos de secuencia , todos los efectos secundarios de las evaluaciones anteriores deberán estar completos y no se habrá producido ningún efecto secundario de las evaluaciones posteriores. (§1.9 / 7)
¿Efectos secundarios? ¿Qué son los efectos secundarios?
La evaluación de una expresión produce algo y, además, si 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 aquí todo bien. Pasando a los puntos de secuencia. Una definición alternativa de puntos seq dada por el autor 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 enumerados en el estándar 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 un tipo primitivo))
en una llamada a la función (si la función está en línea o no), después de la evaluación de todos los argumentos de la 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éxicamente parte de la expresión completa. Por ejemplo, se considera que las subexpresiones involucradas en la evaluación de expresiones de argumento predeterminadas (8.3.6) se crean 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 los 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?
El Estándar define Comportamiento Indefinido en la Sección §1.3.12
como
comportamiento, tal como podría surgir con el uso de una construcción de programa errónea o datos erróneos, para lo cual esta Norma Internacional no impone requisitos 3 .
También se puede esperar un comportamiento indefinido cuando esta Norma Internacional omite la descripción de cualquier definición explícita de comportamiento.
3: el comportamiento indefinido permitido varía desde ignorar la situación por completo con resultados impredecibles, hasta 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 suceder, desde demonios volando por la nariz hasta que tu novia quede embarazada.
¿Cuál es la relación entre el comportamiento indefinido y los puntos de secuencia?
Antes de entrar en eso, debe conocer la (s) diferencia (s) entre Comportamiento indefinido, Comportamiento no especificado e Comportamiento definido de implementación .
También debes saber eso 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 el siguiente, un objeto escalar tendrá su valor almacenado modificado como máximo una vez mediante 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, next sequence point
usualmente está en el punto y coma final, y previous sequence point
está al final de la declaración anterior. Una expresión también puede contener intermedios sequence points
.
De la oración anterior, las siguientes expresiones invocan 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, se debe acceder al valor anterior solo para determinar el valor a 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. Entonces está bien.
Esta regla efectivamente restringe las expresiones legales a aquellas en las que los accesos preceden demostrablemente a 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
no está permitido 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 de nuevo en i++
), por lo que no hay una buena manera de definirlo, ya sea 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
Siga la respuesta para C ++ 11 aquí .
*p++ = 4
no es un comportamiento indefinido.*p++
se interpreta como*(p++)
.p++
devuelvep
(una copia) y el valor almacenado en la dirección anterior. ¿Por qué invocaría eso a UB? Está perfectamente bien.++i
y la asignación ai
. La segunda expresión no invoca a UB porque la expresióni
no cambia el valor dei
. En el segundo ejemplo,i++
le sigue un punto de secuencia (,
) antes de llamar al operador de asignación.Este es un seguimiento de mi respuesta anterior y contiene material relacionado con C ++ 11. .
Prerrequisitos : Un conocimiento elemental de Relaciones (Matemáticas).
¿Es cierto que no hay puntos de secuencia en C ++ 11?
¡Si! Esto es muy cierto.
Puntos de secuencia se han sustituido por Secuenciado Antes y secuenciado Después (y no secuenciados y indeterminadamente Secuenciado ) las relaciones 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 hilo e induce un estricto orden parcial 1
Formalmente significa dar dos evaluaciones cualesquiera (ver más abajo)
A
yB
, siA
se secuencia antesB
, entonces la ejecución deA
deberá preceder a la ejecución deB
. SiA
no está secuenciado antesB
yB
no está secuenciado antesA
, entoncesA
y noB
están secuenciados 2 .Las evaluaciones
A
yB
se secuencian de manera indeterminada cuandoA
se secuencia antesB
oB
se secuencia antesA
, pero no se especifica qué 3 .[Notas]
1: Una orden parcial estricto es una relación binaria
"<"
sobre un conjuntoP
que esasymmetric
, ytransitive
, es decir, para todosa
,b
yc
enP
, tenemos que:........ (i). si a <b entonces ¬ (b <a) (
asymmetry
);........ (ii). si a <b y b <c entonces a <c (
transitivity
).2: La ejecución de evaluaciones no secuenciadas puede superponerse .
3: Las evaluaciones secuenciadas indeterminadamente 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:
cálculos de valor (incluida la determinación de la identidad de un objeto para la evaluación de valor de gl y la obtención de un valor previamente asignado a un objeto para la evaluación de valor ) y
iniciación de efectos secundarios .
Ahora (§1.9 / 14) dice:
Ejemplo trivial:
int x;
x = 10;
++x;
El cálculo del valor y el efecto secundario asociado con
++x
se secuencian después del cálculo del valor y el efecto secundario dex = 10;
Entonces, debe haber alguna relación entre el Comportamiento Indefinido y las cosas mencionadas anteriormente, ¿verdad?
¡Si! Derecha.
En (§1.9 / 15) se ha mencionado que
Por ejemplo :
+
operador no está secuenciada entre sí.<<
y>>
operadores no está secuenciada entre sí.4: En una expresión que se evalúa más de una vez durante la ejecución de un programa, unsequenced y indeterminadamente secuenciaron evaluaciones de sus subexpresiones no tienen que llevarse a cabo constantemente en diferentes evaluaciones.
Eso significa que en
x + y
el cálculo del valor dex
yy
se secuencian antes del cálculo del valor de(x + y)
.Más importante
Ejemplos:
i = i++ * ++i; // Undefined Behaviour
i = ++i + i++; // Undefined Behaviour
i = ++i + ++i; // Undefined Behaviour
i = v[i++]; // Undefined Behaviour
i = v[++i]: // Well-defined Behavior
i = i++ + 1; // Undefined Behaviour
i = ++i + 1; // Well-defined Behaviour
++++i; // Well-defined Behaviour
f(i = -1, i = -1); // Undefined Behaviour (see below)
Expresiones
(5)
,(7)
y(8)
no invocan comportamientos indefinidos. Consulte las siguientes respuestas para obtener una explicación más detallada.Nota final :
Si encuentra algún defecto en la publicación, deje un comentario. Usuarios avanzados (con rep> 20000) no duden en editar la publicación para corregir errores tipográficos y otros errores.
fuente
f(i = -1, i = 1)
?++i
(siendo evaluado el valor antes del+
operador que lo está usando), el estándar todavía no dice que su efecto secundario debe ser terminado. Pero, de hecho, debido a que devuelve una referencia a unalvalue
que es eni
sí misma, DEBE haber terminado el efecto secundario ya que la evaluación debe estar terminada, por lo tanto, el valor debe estar actualizado. Esta fue la parte loca de hecho.C ++ 17 (
N4659
) incluye una propuesta de Orden de evaluación de expresión de refinación para Idiomatic C ++ que define un orden de evaluación de expresión más estricto.En particular, la siguiente oración
junto con la siguiente aclaración
Hacer válidos varios casos de comportamiento previamente indefinido, incluido el en cuestión:
Sin embargo, varios otros casos similares todavía conducen a un comportamiento indefinido.
En
N4140
:Pero en
N4659
Por supuesto, el uso de un compilador compatible con C ++ 17 no significa necesariamente que uno deba comenzar a escribir tales expresiones.
fuente
i = i++ + 1;
se define el comportamiento en c ++ 17, creo que incluso si "El operando derecho se secuencia antes que el operando izquierdo", sin embargo, la modificación de "i ++" y el efecto secundario para la asignación no están secuenciados, proporcione más detalles para interpretarlosSupongo que hay una razón fundamental para el cambio, no es meramente cosmético aclarar la vieja 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 específico, es posible una evaluación concurrente: no es así con las viejas reglas. Por ejemplo en:
anteriormente, ya sea a b, o, b entonces a. Ahora, ayb pueden evaluarse con instrucciones intercaladas o incluso en diferentes núcleos.
fuente
En lo
C99(ISO/IEC 9899:TC3)
que parece ausente de esta discusión hasta ahora, se hacen los siguientes steteents con respecto al orden de evaluación.fuente