c++ - Comportamiento indefinido y puntos de secuencia recargados
undefined-behavior c++-faq (5)
Bien. Después de pasar por las respuestas anteriores, volví a pensar en mi propia pregunta, particularmente en esta parte que solo Noah intentó answer pero no estoy completamente convencido con él.
a[++i] = i;
Caso 1:
Si a
es una matriz de tipo incorporado. Entonces lo que dijo Noah es correcto. Es decir,
a [++ i] = i no es tan afortunado suponiendo que a es tu tipo de matriz básica,
o incluso un usuario definido. El problema que tienes aquí es que no sabemos qué parte de la expresión que contiene i se evalúa primero.
Entonces a[++i]=i
invoca comportamiento indefinido, o el resultado no está especificado. Sea lo que sea, ¡no está bien definido!
PD: en la cita anterior, huelga es por supuesto mio
Caso 2
Si a
es un objeto de tipo definido por el usuario que sobrecarga al operator[]
, entonces hay dos casos.
- Si el tipo de devolución de la función
operator[]
sobrecargado es de tipo incorporado, de nuevo,a[++i]=i
invoca un comportamiento indefinido o el resultado no se especifica. - Pero si el tipo de retorno de la función
operator[]
sobrecargado es un tipo definido por el usuario, entonces el comportamiento dea[++i] = i
está bien definido (hasta donde yo entiendo), ya que en este caso esa[++i]=i
es equivalente a escribira.operator[](++i).operator=(i);
que es lo mismo que,a[++i].operator=(i);
. Es decir, eloperator=
asignaciónoperator=
se invoca en el objeto devuelto dea[++i]
, que parece estar muy bien definido, ya que para cuandoa[++i]
retorna,++i
ya he sido evaluado, y luego el objeto devuelto llamaoperator=
función pasando el valor actualizado dei
a él como argumento. Tenga en cuenta que hay un punto de secuencia entre estas dos llamadas . Y la sintaxis asegura que no hay competencia entre estas dos llamadas, y eloperator[]
sería invocado primero, y consecutivamente, el argumento++i
pasado a él, también sería evaluado primero.
Piense en esto como someInstance.Fun(++k).Gun(10).Sun(k).Tun();
en el que cada llamada de función consecutiva devuelve un objeto de algún tipo definido por el usuario. Para mí, esta situación se parece más a esto: eat(++k);drink(10);sleep(k)
, porque en ambas situaciones, existe un punto de secuencia después de cada llamada a la función.
Por favor corrígeme si estoy equivocado. :-)
Considere este tema como una continuación del siguiente tema:
Cuota anterior
Comportamiento indefinido y puntos de secuencia
Volvamos a esta expresión divertida y enrevesada (las frases en cursiva están tomadas del tema anterior * smile *):
i += ++i;
Decimos que esto invoca un comportamiento indefinido. Supongo que cuando dice esto, suponemos implícitamente que ese tipo de i
es uno de los tipos incorporados.
¿Qué pasa si el tipo de i
es un tipo definido por el usuario? Supongamos que su tipo es Index
que se define más adelante en esta publicación (ver a continuación). ¿Todavía invocaría un comportamiento indefinido?
¿Si es así por qué? ¿No es equivalente a escribir i.operator+=(i.operator++());
o incluso sintácticamente más simple i.add(i.inc());
? O, ¿ellos también invocan un comportamiento indefinido?
Si no, ¿por qué no? Después de todo, el objeto se modifica dos veces entre puntos de secuencia consecutivos. Recuerde la regla de oro: una expresión puede modificar el valor de un objeto solo una vez entre "puntos de secuencia" consecutivos . Y si i += ++i
es una expresión, entonces debe invocar el comportamiento indefinido. De ser así, sus equivalentes i.operator+=(i.operator++());
y i.add(i.inc());
también debe invocar undefined-behavior, que parece ser falso (hasta donde yo entiendo)
O, i += ++i
no es una expresión para empezar? Si es así, ¿qué es y cuál es la definición de expresión ?
Si se trata de una expresión y, al mismo tiempo, su comportamiento también está bien definido, implica que el número de puntos de secuencia asociados con una expresión depende de algún modo del tipo de operandos implicados en la expresión. ¿Estoy en lo correcto (incluso parcialmente)?
Por cierto, ¿qué tal esta expresión?
//Consider two cases:
//1. If a is an array of a built-in type
//2. If a is user-defined type which overloads the subscript operator!
a[++i] = i; //Taken from the previous topic. But here type of `i` is Index.
Debe considerar esto también en su respuesta (si conoce su comportamiento con seguridad). :-)
Es
++++++i;
bien definido en C ++ 03? Después de todo, esto es esto,
((i.operator++()).operator++()).operator++();
class Index
{
int state;
public:
Index(int s) : state(s) {}
Index& operator++()
{
state++;
return *this;
}
Index& operator+=(const Index & index)
{
state+= index.state;
return *this;
}
operator int()
{
return state;
}
Index & add(const Index & index)
{
state += index.state;
return *this;
}
Index & inc()
{
state++;
return *this;
}
};
Como otros han dicho, su ejemplo de i += ++i
funciona con el tipo definido por el usuario ya que está llamando a funciones, y las funciones comprenden puntos de secuencia.
Por otro lado, a[++i] = i
no es tan afortunado suponiendo que a
es tu tipo de matriz básica, o incluso una definida por el usuario. El problema que tienes aquí es que no sabemos qué parte de la expresión que contiene i
se evalúa primero. Podría ser que ++i
se evalúe, se pase al operator[]
(o la versión cruda) para recuperar el objeto allí, y luego se pasa el valor de i
(que es después de que se incrementó). Por otro lado, quizás el último lado se evalúa primero, se almacena para una asignación posterior y luego se evalúa la parte ++i
.
Creo que está bien definido:
Del borrador del estándar de C ++ (n1905) §1.9 / 16:
"También hay un punto de secuencia después de la copia de un valor devuelto y antes de la ejecución de cualquier expresión fuera de la función13). Varios contextos en C ++ causan la evaluación de una llamada de función, aunque no aparece la sintaxis de llamada de función correspondiente en la unidad de traducción. [ Ejemplo : la evaluación de una nueva expresión invoca una o más funciones de asignación y construcción; véase 5.3.4. Para otro ejemplo, la invocación de una función de conversión (12.3.2) puede surgir en contextos en los que no aparece la sintaxis de llamada de función. ejemplo ] Los puntos de secuencia en entrada de función y salida de función (como se describió anteriormente) son características de las llamadas de función como evaluadas, cualquiera que sea la sintaxis de la expresión que llama a la función. "
Tenga en cuenta la parte que en negrita. Esto significa que de hecho hay un punto de secuencia después de la llamada a la función de incremento ( i.operator ++()
) pero antes de la llamada de asignación compuesta ( i.operator+=
).
Parece que el código
i.operator+=(i.operator ++());
Funciona perfectamente bien con respecto a los puntos de secuencia. La sección 1.9.17 del estándar C ++ ISO dice esto acerca de los puntos de secuencia y la evaluación de la función:
Cuando se llama a una función (ya sea que la función esté o no en línea), hay un punto de secuencia después de la evaluación de todos los argumentos de funciones (si los hay) que tienen lugar antes de la ejecución de cualquier expresión o declaración en el cuerpo de la función. También hay un punto de secuencia después de la copia de un valor devuelto y antes de la ejecución de cualquier expresión fuera de la función.
Esto indicaría, por ejemplo, que i.operator ++()
como parámetro para operator +=
tiene un punto de secuencia después de su evaluación. En resumen, dado que los operadores sobrecargados son funciones, se aplican las reglas de secuencia normales.
¡Gran pregunta, por cierto! Realmente me gusta cómo me obligas a comprender todos los matices de un idioma que ya creía conocer (y pensé que creía que lo sabía). :-)
http://www.eelis.net/C++/analogliterals.xhtml Literales analógicos viene a mi mente
unsigned int c = ( o-----o
| !
! !
! !
o-----o ).area;
assert( c == (I-----I) * (I-------I) );
assert( ( o-----o
| !
! !
! !
! !
o-----o ).area == ( o---------o
| !
! !
o---------o ).area );