Comportamiento indefinido y puntos de secuencia recargados

84

Considere este tema como una secuela del siguiente tema:

Entrega anterior
Comportamiento indefinido y puntos de secuencia

Repasemos esta expresión divertida y complicada (las frases en cursiva se tomaron del tema anterior * sonrisa *):

i += ++i;

Decimos que esto invoca un comportamiento indefinido. Supongo que cuando digo esto, asumimos implícitamente que el tipo de ies uno de los tipos integrados.

¿Qué pasa si el tipo de ies un tipo definido por el usuario? Digamos que su tipo es el Indexque se define más adelante en esta publicación (ver más abajo). ¿Seguiría invocando 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 también invocan un comportamiento indefinido?

Si no, ¿por qué no? Después de todo, el objeto ise modifica dos veces entre puntos de secuencia consecutivos. Recuerde la regla general: una expresión puede modificar el valor de un objeto solo una vez entre "puntos de secuencia consecutivos" . Y si i += ++ies una expresión, debe invocar un comportamiento indefinido. Si es así, sus equivalentes i.operator+=(i.operator++());y i.add(i.inc());también debe invocar un comportamiento indefinido que parece ser falso! (hasta donde yo entiendo)

¿O i += ++ino es una expresión para empezar? Si es así, ¿qué es y cuál es la definición de expresión ?

Si es una expresión y, al mismo tiempo, su comportamiento también está bien definido, entonces implica que el número de puntos de secuencia asociados con una expresión depende de alguna manera del tipo de operandos involucrados en la expresión. ¿Estoy en lo correcto (incluso en parte)?


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.

También debe considerar esto 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;
        }
};
Nawaz
fuente
13
+1 gran pregunta, que inspiró grandes respuestas. Creo que debería decir que sigue siendo un código horrible que debería refactorizarse para que sea más legible, pero probablemente lo sepas de todos modos :)
Philip Potter
4
@ ¿Cuál es la pregunta: quién dijo que es igual? o quien dijo que no es lo mismo ¿No depende de cómo los implementes? (Nota: ¡supongo que el tipo de ses un tipo definido por el usuario!)
Nawaz
5
No veo que ningún objeto escalar se modifique dos veces entre dos puntos de secuencia ...
Johannes Schaub - litb
3
@Johannes: entonces se trata de un objeto escalar . ¿Qué es? Me pregunto por qué nunca había oído hablar de él antes. ¿Quizás porque los tutoriales / C ++ - faq no lo mencionan, o no lo enfatizan? ¿Es diferente de los objetos de tipo incorporado ?
Nawaz
3
@Phillip: Obviamente, no voy a escribir ese código en la vida real; de hecho, ningún programador en su sano juicio lo va a escribir. ¡Estas preguntas generalmente están diseñadas para que podamos comprender mejor todo el asunto del comportamiento indefinido y los puntos de secuencia! :-)
Nawaz

Respuestas:

48

Parece 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 ISO C ++ dice esto sobre los puntos de secuencia y la evaluación de funciones:

Cuando se llama a una función (esté o no en línea), hay un punto de secuencia 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. 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 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 secuenciación normales.

¡Gran pregunta, por cierto! Me gusta mucho cómo me estás obligando a entender todos los matices de un idioma que ya pensé que conocía (y pensé que pensé que sabía). :-)

templatetypedef
fuente
12

http://www.eelis.net/C++/analogliterals.xhtml Me vienen a la mente literales analógicos

  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 );
Antidepresivo industrial
fuente
Había una pregunta, es ++++++ i; bien definido en C ++ 03?
Antidepresivo industrial
11

Como han dicho otros, su i += ++iejemplo 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] = ino es tan afortunado asumir que aes su tipo de matriz básico, o incluso uno definido por el usuario. El problema que tiene aquí es que no sabemos qué parte de la expresión que contiene ise evalúa primero. Podría ser que ++ise evalúe, se pase a operator[](o la versión sin procesar) para recuperar el objeto allí, y luego el valor de ise pase a ese (que es después de que se incrementó i). Por otro lado, quizás el último lado se evalúe primero, se almacene para una asignación posterior y luego ++ise evalúe la parte.

Edward extraño
fuente
entonces ... ¿el resultado es, por tanto, no especificado en lugar de UB, ya que el orden de evaluación de las expresiones no está especificado?
Philip Potter
@Philip: unspecified significa que esperamos que el compilador especifique el comportamiento, mientras que undefined no impone tal obligación. Creo que no está definido aquí, para que los compiladores tengan más espacio para las optimizaciones.
Matthieu M.
@Noah: También publiqué una respuesta. Por favor, échale un vistazo y déjame saber tus pensamientos. :-)
Nawaz
1
@Philip: el resultado es UB, debido a la regla en 5/4: "Los requisitos de este párrafo deben cumplirse para cada ordenamiento permitido de las subexpresiones de una expresión completa; de lo contrario, el comportamiento no está definido". Si todos los ordenamientos permitidos tuvieran puntos de secuencia entre la modificación ++iy la lectura de ien el RHS de la asignación, entonces el orden no estaría especificado. Debido a que uno de los ordenamientos permitidos hace esas dos cosas sin un punto de secuencia intermedio, el comportamiento no está definido.
Steve Jessop
1
@Philip: No solo define el comportamiento no especificado como comportamiento indefinido. Nuevamente, si el rango de comportamiento no especificado incluye alguno que no está definido, entonces el comportamiento general no está definido. Si el rango de comportamiento no especificado se define en todas las posibilidades, entonces el comportamiento general no se especifica. Pero tienes razón en el segundo punto, estaba pensando en un usuario aintegrado y definido por el usuario i.
Steve Jessop
8

Creo que está bien definido:

Del borrador del estándar 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 ++ provocan 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 constructor; consulte 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. - end ejemplo ] Los puntos de secuencia en la entrada de función y la salida de función (como se describe arriba) son características de las llamadas de función evaluadas, cualquiera que sea la sintaxis de la expresiónque llama a la función podría ser. "

Tenga en cuenta la parte en negrita. Esto significa que de hecho hay un punto de secuencia después de la llamada de función de incremento ( i.operator ++()) pero antes de la llamada de asignación compuesta ( i.operator+=).

Matthew Flaschen
fuente
6

Bien. Después de repasar las respuestas anteriores, volví a pensar en mi propia pregunta, particularmente en esta parte que solo Noah intentó responder, pero no estoy completamente convencido con él.

a[++i] = i;

Caso 1:

Si aes una matriz de tipo incorporado. Entonces lo que dijo Noah es correcto. Es decir,

a [++ i] = i no es tan afortunado asumiendo que a es su tipo de matriz básica, o incluso uno definido por el usuario . El problema que tiene aquí es que no sabemos qué parte de la expresión que contiene i se evalúa primero.

Entonces a[++i]=iinvoca un comportamiento indefinido, o el resultado no está especificado. Sea lo que sea, ¡no está bien definido!

PD: en la cita anterior, tachado es por supuesto mío.

Caso 2:

Si aes un objeto de tipo definido por el usuario que sobrecarga el operator[], entonces nuevamente hay dos casos.

  1. Si el tipo de retorno de la operator[]función sobrecargada es un tipo integrado, de nuevo a[++i]=iinvoca un comportamiento indefinido o el resultado no está especificado.
  2. Pero si el tipo de retorno de la operator[]función sobrecargada es un tipo definido por el usuario, entonces el comportamiento de a[++i] = iestá bien definido (hasta donde tengo entendido), ya que en este caso a[++i]=ies equivalente a escribir a.operator[](++i).operator=(i);que es lo mismo que a[++i].operator=(i);,. Es decir, la asignación operator=se invoca en el objeto devuelto de a[++i], que parece estar muy bien definido, ya que para el momento en que los a[++i]retornos ++iya se han evaluado, y luego el objeto devuelto llama a la operator=función y le pasa el valor actualizado de icomo argumento. Tenga en cuenta que hay un punto de secuencia entre estas dos llamadas . Y la sintaxis asegura que no haya competencia entre estas dos llamadas, yoperator[]se invocaría primero, y consecutivamente, el argumento que se le ++ipasó, también se evaluaría 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 de función.

Por favor corrígeme si estoy equivocado. :-)

Nawaz
fuente
1
@Nawaz k++y nok están separados por puntos de secuencia. Ambos pueden ser evaluados antes de o son evaluados. El lenguaje solo requiere que se evalúe antes , no que los argumentos se evalúen antes que los argumentos. Estoy volviendo a explicar lo mismo sin poder dar una referencia, así que no vamos a progresar desde aquí. SunFunFunSunFunSun
Philip Potter
1
@Nawaz: porque no hay nada que defina un punto de secuencia que los separe. Hay puntos de secuencia antes y después de la Sunejecución, pero Funel argumento ++kpuede evaluarse antes o después de eso. Hay puntos de secuencia antes y después de la Funejecución, pero Sunel argumento kpuede evaluarse antes o después de eso. Por lo tanto, un caso posible es que ambos ky ++kse evalúan antes que Suno Funse evalúan, por lo que ambos están antes de los puntos de secuencia de llamada de función, por lo que no hay un punto de secuencia que separe ky ++k.
Philip Potter
1
@Philip: Repito: ¿en qué se diferencia esta situación eat(i++);drink(10);sleep(i);? ... incluso ahora, ¿podría decirse que i++puede ser evaluado antes o después de eso?
Nawaz
1
@Nawaz: ¿cómo puedo ser más claro? En el ejemplo de Fun / Sun, no hay un punto de secuencia entre ky ++k. En el ejemplo de comer / beber, no es como punto de secuencia entre iy i++.
Philip Potter
3
@Philip: eso no tiene ningún sentido. Entre Fun () y Sun () existe un punto de secuencia, pero entre su argumento no existen puntos de secuencia. Es como decir, entre eat()y sleep()existe punto (s) de secuencia, pero entre hay argumentos ni siquiera uno. ¿Cómo pueden los argumentos de dos llamadas a funciones separadas por puntos de secuencia pertenecer a los mismos puntos de secuencia?
Nawaz