hongo mental
En el siguiente código, una función miembro set()
es llamado en un model
, que es un puntero nulo. Esto sería un comportamiento indefinido. Sin embargo, el parámetro de la función miembro es el resultado de otra llamada de función que verifica si el model
es un puntero nulo y se lanza en ese caso. ¿Está garantizado que estimate()
siempre será llamado antes de la model
se accede o sigue siendo un comportamiento indefinido (UB)?
#include <iostream>
#include <memory>
#include <vector>
struct Model
{
void set(int x)
{
v.resize(x);
}
std::vector<double> v;
};
int estimate(std::shared_ptr<Model> m)
{
return m ? 3 : throw std::runtime_error("Model is not set");
}
int main()
{
try
{
std::shared_ptr<Model> model; // null pointer here
model->set(estimate(model));
}
catch (const std::runtime_error& e)
{
std::cout << e.what();
}
return 0;
}
jason
Esto sigue siendo un comportamiento indefinido (UB) según expr.compuesto:
El postfijo-expresión se secuencia antes de cada expresión en el lista de expresiones y cualquier argumento predeterminado. La inicialización de un parámetro, incluidos todos los cálculos de valores asociados y los efectos secundarios, tiene una secuencia indeterminada con respecto a la de cualquier otro parámetro.
(énfasis mío)
Esto significa que la expresión posfija model->set
se secuencia antes de la expresión estimate(model)
en la lista de expresiones. Y desde model
es puntero nulo, la condición previa de std::shared_ptr::operator->
es violado y por lo tanto esto conduce a UB.
-
Entonces, si lo hago bien desde
model->
es equivalente a(*model).
esto significa quemodel
todavía está desreferenciado y, por lo tanto, este sigue siendo un comportamiento indefinido.– Vale
9 de mayo a las 9:27
-
No está muy claro cómo UB se deduce de esta declaración. Se evalúa la expresión de sufijo antes de la flecha, lo que da como resultado un puntero nulo. Junto con la expresión de identificación, determina el resultado de la expresión completa. Ok, pero ¿elimina la referencia al puntero nulo para determinarlo?
– hongo mental
9 de mayo a las 10:25
-
@mentalmushroom si
set
esvirtual
el puntero tiene que ser desreferenciado para enviar la llamada en función del tipo dinámico del objeto. Este caso no está señalado en la norma (a pesar de que un caso novirtual
función podría despacharse basándose únicamente en el tipo estático demodel
), por lo tanto, el caso general es que el puntero tiene ser desreferenciable.– Quintín
9 de mayo a las 13:45
-
@mentalmushroom Tu uso de
std::shared_ptr
hace esto bastante claro, porque elstd::shared_ptr::operator->
la sobrecarga tiene una condición previa por especificación de biblioteca de que el puntero no es nulo. la llamada a eso Por lo tanto, la función ya tiene un comportamiento indefinido, independientemente de la evaluación de->set
sobre el resultado Si lo mismo sería cierto con un puntero nulo sin procesar es una pregunta diferente.– usuario17732522
9 de mayo a las 14:46
-
@ user17732522 He agregado una segunda parte en mi respuesta que usa expr.ref. Básicamente a mi entender de [expr.ref]: “la expresión posfijo
model
antes de que se evalúe la flecha y el resultado de esa evaluación (que esnullptr
) junto con la expresión-idoperator->
se utiliza para determinar el resultado de toda la expresión del sufijo”. Esto conduce a UB ya que se viola la condición previa. ¿Es la conclusión y el análisis usando [expr.ref] ¿correcto?– Jasón
9 de mayo a las 15:05
A mi entender, este es un comportamiento indefinido. al menos desde C++ 17:
- En una expresión de llamada a función, la expresión que nombra la función se secuencia antes de cada expresión de argumento y cada argumento predeterminado.
Como interpreto esto, en realidad garantiza que model->set
se evalúa antes de cualquier argumento y, por lo tanto, invoca un comportamiento indefinido. No importa si o no model
es un puntero crudo.
-
Pero
model->set
nombra una función miembro y no una función, por lo que esto no parece aplicarse. El título de la pregunta también sugiere explícitamente que OP está interesado en el caso de la función miembro y ya conoce el caso de la función no miembro.– Vale
9 de mayo a las 9:32
-
@Val Entonces, ¿una función miembro no es una función? ¿En qué basas esa afirmación?
– nielsen
9 de mayo a las 9:36
-
La llamada de función y la llamada de función miembro son dos cosas diferentes.
– Vale
9 de mayo a las 9:39
-
@Val Esto es cppreference, no el estándar. Este texto está escrito para facilitar la lectura, no para la máxima precisión. El regla real dice que el postfijo-expresión se secuencia antes de las inicializaciones del argumento. La regla citada de cppreference se aplica aquí.
– j6t
9 de mayo a las 10:18
-
@Val Incluso en el estándar, creo que la palabra “función” cubre las funciones de los miembros, así como las funciones de los no miembros, y se hace una distinción explícita cuando corresponde. En términos generales, una “llamada a una función miembro” es un caso especial de una “llamada a una función”.
– nielsen
9 de mayo a las 11:01
El postfijo-expresión se secuencia antes de cada expresión en el lista de expresiones y cualquier argumento predeterminado.
En este caso, esto significa que model->set
se evalúa antes estimate(model)
.
Desde model
es un shared_ptr<Model>
, model->set
usos shared_ptr
está sobrecargado operator->
que tiene la siguiente precondición ([util.smartptr.shared.obs]/5):
Condiciones previas:
get() != nullptr
.
La violación de esta condición previa da como resultado un comportamiento indefinido ([structure.specifications]/3.3).
Bueno el
estimate
se garantiza que la llamada se llamará primero. Lo que significa que la excepción se lanzará antes de que ocurra la desreferencia real del puntero nulo. ¿El código sigue siendo UB incluso cuando el código que causa la UB no se ejecuta? Esa es una pregunta interesante… 🙂– Un tipo programador
9 de mayo a las 8:33
¿Su pregunta es puramente por curiosidad o se basa en un código real? No organizaría mi código basándome en el orden de evaluación…
– Mickaël C. Guimarães
9 de mayo a las 8:34
Creo que es perfectamente razonable escribir código basado en el hecho de que los argumentos de la función se evaluarán antes que la función misma.
– John
9 de mayo a las 8:36
@463035818_is_not_a_number: de ahí mi comentario.
– Yves Daoust
9 de mayo a las 8:41
FWIW, desinfectante de direcciones dice
->
se evalúa de todos modos godbolt.org/z/n13P18M3G. Además, el uso de punteros en bruto (que básicamente deberían ser el mismo problema aquí) da el mismo diagnóstico godbolt.org/z/TPT7Y89cG– cién
9 de mayo a las 9:25