c++ - tipos - funciones simples programacion
¿Cuándo la invocación de una función miembro en una instancia nula da como resultado un comportamiento indefinido? (2)
Ambos (a)
y (b)
dan como resultado un comportamiento indefinido. Siempre es un comportamiento indefinido llamar a una función miembro a través de un puntero nulo. Si la función es estática, tampoco está técnicamente definida, pero existe cierta disputa.
Lo primero que debes entender es por qué es un comportamiento indefinido desreferenciar un puntero nulo. En C ++ 03, en realidad hay un poco de ambigüedad aquí.
Aunque "desreferenciar un puntero nulo resulta en un comportamiento indefinido" se menciona en las notas en §1.9 / 4 y §8.3.2 / 4, nunca se menciona explícitamente. (Las notas no son normativas)
Sin embargo, uno puede intentar deducirlo de §3.10 / 2:
Un lvalue se refiere a un objeto o función.
Al desreferenciar, el resultado es un valor l. Un puntero nulo no se refiere a un objeto, por lo tanto, cuando usamos el valor l tenemos un comportamiento indefinido. El problema es que la oración anterior nunca se menciona, entonces, ¿qué significa "usar" el valor l? ¿Solo generarlo en absoluto, o usarlo en el sentido más formal de realizar la conversión lvalue a rvalue?
De todos modos, definitivamente no se puede convertir a un valor r (§4.1 / 1):
Si el objeto al que se refiere lvalue no es un objeto de tipo T y no es un objeto de un tipo derivado de T, o si el objeto no está inicializado, un programa que necesita esta conversión tiene un comportamiento indefinido.
Aquí definitivamente es un comportamiento indefinido.
La ambigüedad proviene de si es o no un comportamiento indefinido para deferencia, pero no utiliza el valor de un puntero no válido (es decir, obtiene un lvalue pero no lo convierte en un valor r). Si no, entonces int *i = 0; *i; &(*i);
int *i = 0; *i; &(*i);
está bien definido Este es un problema activo .
Por lo tanto, tenemos una vista estricta de "quitar la referencia a un puntero nulo, obtener un comportamiento indefinido" y una vista débil de "usar un puntero nulo sin referencias, obtener un comportamiento no definido".
Ahora consideramos la pregunta.
Sí, (a)
da como resultado un comportamiento indefinido. De hecho, si this
es nulo, independientemente del contenido de la función, el resultado no está definido.
Esto se desprende de §5.2.5 / 3:
Si
E1
tiene el tipo "puntero a clase X", entonces la expresiónE1->E2
se convierte a la forma equivalente(*(E1)).E2;
*(E1)
dará como resultado un comportamiento indefinido con una interpretación estricta, y .E2
convierte en un valor r, lo que lo convierte en un comportamiento indefinido para la interpretación débil.
También se sigue que es un comportamiento indefinido directamente de (§9.3.1 / 1):
Si se llama a una función miembro no estático de una clase X para un objeto que no es de tipo X, o de un tipo derivado de X, el comportamiento no está definido.
Con funciones estáticas, la interpretación estricta contra débil hace la diferencia. Estrictamente hablando, no está definido:
Se puede hacer referencia a un miembro estático utilizando la sintaxis de acceso de miembro de clase, en cuyo caso se evalúa la expresión de objeto.
Es decir, se evalúa como si no fuera estático y una vez más desreferenciamos un puntero nulo con (*(E1)).E2
.
Sin embargo, dado que E1
no se utiliza en una llamada de función de miembro estática, si usamos la interpretación débil, la llamada está bien definida. *(E1)
da como resultado un valor l, la función estática se resuelve, *(E1)
se descarta y se llama a la función. No hay conversión lvalue-r-value, por lo que no hay un comportamiento indefinido.
En C ++ 0x, a partir de n3126, la ambigüedad permanece. Por ahora, sé seguro: usa la interpretación estricta.
Considera el siguiente código:
#include <iostream>
struct foo
{
// (a):
void bar() { std::cout << "gman was here" << std::endl; }
// (b):
void baz() { x = 5; }
int x;
};
int main()
{
foo* f = 0;
f->bar(); // (a)
f->baz(); // (b)
}
Esperamos que (b)
bloquee, porque no hay un miembro correspondiente x
para el puntero nulo. En la práctica, (a)
no falla porque this
puntero nunca se usa.
Como (b)
desreferencia el puntero this
( (*this).x = 5;
), y this
es nulo, el programa ingresa un comportamiento indefinido, ya que desreferenciar nulo siempre se dice que es un comportamiento indefinido.
¿ (a)
resultado un comportamiento indefinido? ¿Qué pasa si ambas funciones ( x
) son estáticas?
Obviamente indefinido significa que no está definido , pero a veces puede ser predecible. La información que voy a proporcionar nunca se debe confiar para el código de trabajo, ya que ciertamente no está garantizado, pero podría ser útil cuando se depura.
Podría pensar que llamar a una función en un puntero de objeto desreferenciará el puntero y causará UB. En la práctica, si la función no es virtual, el compilador la convertirá en una llamada de función simple pasando el puntero como el primer parámetro, omitiendo la desreferencia y creando una bomba de tiempo para la función del miembro llamado. Si la función miembro no hace referencia a ninguna variable miembro o función virtual, en realidad podría tener éxito sin error. ¡Recuerda que el éxito cae dentro del universo de "indefinido"!
La función MFC GetSafeHwnd realmente se basa en este comportamiento. No sé lo que estaban fumando.
Si está llamando a una función virtual, se debe quitar la referencia del puntero para acceder al vtable y, de seguro, obtendrá UB (probablemente una falla, pero recuerde que no hay garantías).