Considere 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 (b) bloqueo, porque no hay ningún miembro correspondiente x para el puntero null. En la práctica, (a) no se cae porque el this puntero no se utiliza nunca.

Porque (b) elimina referencias a la this puntero ((*this).x = 5;), y this es null, el programa entra en un comportamiento indefinido, como eliminar la referencia null es siempre dijo ser un comportamiento indefinido.

Hace (a) resultar en un comportamiento indefinido? ¿Y si ambas funciones (y x) son estáticos?

Si ambas funciones son static, ¿cómo podría x ser remitidos dentro de baz? (x es un miembro no estática variable)
Pretender x se hizo demasiado estático. 🙂
Seguramente, pero para el caso (a) funciona de la misma en todos los casos, yo.e, se obtiene la función invocada. Sin embargo, sustituya el valor del puntero de 0 a 1 (es decir, a través de reinterpret_cast), casi invariablemente, siempre se bloquea. ¿El valor de la asignación de 0 y por lo tanto NULO, como en el caso a, representa algo especial para el compilador ? ¿Por qué siempre accidente con cualquier otro valor asignado a la misma?
Interesante: Venga la próxima revisión de C++, no habrá más de eliminar de los punteros. Vamos ahora llevar a cabo el direccionamiento indirecto a través de punteros. Para obtener más información, por favor realizar el direccionamiento indirecto a través de este enlace: N3362
La invocación de una función miembro de un puntero nulo es siempre un comportamiento indefinido. Con sólo mirar su código, ya puedo sentir el comportamiento indefinido arrastrándose lentamente hasta mi cuello!

OriginalEl autor GManNickG | 2010-03-18

2 Comentarios

  1. 104

    Tanto (a) y (b) resultar en un comportamiento indefinido. Es siempre un comportamiento indefinido para llamar a una función miembro a través de un puntero null. Si la función es estática, es técnicamente indefinido, pero hay algunas controversias.


    La primera cosa a entender es por qué es un comportamiento indefinido para desreferenciar un puntero null. En C++03, de hecho, hay un poco de ambigüedad aquí.

    Aunque «desreferenciar un puntero nulo resultado un comportamiento indefinido» es mencionado en las notas en ambos §1.9/4 y §8.3.2/4, nunca de forma explícita. (Las notas no son normativos.)

    Sin embargo, uno puede intentar deducir de §3.10/2:

    Un lvalue se refiere a un objeto o función.

    Al eliminar la referencia, el resultado es un lvalue. Un puntero nulo no referencia a un objeto, por lo tanto, cuando utilizamos el lvalue tenemos un comportamiento indefinido. El problema es que la frase anterior nunca se dice, entonces, ¿qué significa «usar» el lvalue? Sólo incluso generar en absoluto, o para su uso en la más sentido formal de realizar lvalue-a-r-value de conversión?

    Independientemente, definitivamente no se puede convertir a un r-value (§4.1/1):

    Si el objeto al que el lvalue se refiere no es un objeto de tipo T y no es un objeto de un tipo derivado de T, o si el objeto no está inicializada, un programa que requiere esta conversión tiene un comportamiento indefinido.

    Aquí es, definitivamente, un comportamiento indefinido.

    La ambigüedad proviene de si es o no es un comportamiento indefinido a deferencia pero no uso el valor de un puntero no válido (es decir, obtener un lvalue pero no convertirlo en un r-value). Si no, entonces int *i = 0; *i; &(*i); está bien definido. Este es un problema activo.

    Así que tenemos una estricta «desreferenciar un puntero null, obtener un comportamiento indefinido» de la vista y un débil «usar un sin referencia a un puntero nulo, obtener un comportamiento indefinido».

    Ahora consideramos la cuestión.


    Sí, (a) resultados en un comportamiento indefinido. De hecho, si this es null, el independientemente del contenido de la función el resultado es indefinido.

    Esto se deduce de la §5.2.5/3:

    Si E1 de tipo «puntero a la clase X», entonces la expresión E1->E2 se convierte a la forma equivalente (*(E1)).E2;

    *(E1) va a resultar en un comportamiento indefinido con una interpretación estricta, y .E2 convierte en un r-value, lo que es un comportamiento indefinido para los débiles de interpretación.

    Se desprende también que es un comportamiento indefinido directamente de (§9.3.1/1):

    Si una función miembro no estático de una clase X se llama para un objeto que no es de tipo X, o de un tipo derivado de X, el comportamiento es indefinido.


    Con funciones estáticas, la estricta y débiles de la interpretación que hace la diferencia. Estrictamente hablando, es indefinido:

    Un miembro estático puede ser referido a la utilización de la clase de acceso de los miembros de la sintaxis, en cuyo caso el objeto-la expresión es evaluada.

    Que es, se evalúa como si fuera no-estática y una vez más nos desreferenciar un puntero nulo con (*(E1)).E2.

    Sin embargo, debido a E1 no se utiliza en un miembro estático-llamada a la función, si utilizamos la interpretación débil de la llamada está bien definido. *(E1) resultados en un lvalue, la función estática se resuelve, *(E1) se descarta, y se llama a la función. No hay lvalue-a-r-value de conversión, por lo que no hay un comportamiento indefinido.

    En C++0x, como de n3126, la ambigüedad se mantiene. Por ahora, estar seguro: el uso de la interpretación estricta.

    +1. Continuando con el pedante, bajo el «débil» definición de la función miembro no estático no ha sido llamado «para un objeto que no es de tipo X». Ha sido llamado por un lvalue que no es un objeto. Así que la solución propuesta agrega el texto «o si el lvalue es un vacío lvalue» a la cláusula que se cita.
    Podrías aclarar un poco? En particular, con su «asunto cerrado» y «activo » problema» de los enlaces, ¿cuáles son los números de edición? También, si este es un asunto cerrado, ¿qué es exactamente la respuesta de sí o no para las funciones estáticas? Me siento como que me falta el paso final en tratar de entender su respuesta.
    No creo que CWG defecto 315 es «cerrado», como su presencia en el «cierre de temas» en la página implica. La lógica dice que debería ser permitido porque «*p no es un error cuando p es nulo a menos que el lvalue se convierte en un r-value.» Sin embargo, que se basa en el concepto de un «vacío lvalue», que es parte de la propuesta de resolución a CWG defecto 232, pero que no ha sido adoptada. Así, con el lenguaje en C++03 y C++0x, eliminar el puntero nulo es aún indefinido, incluso si no hay ningún lvalue-a-r-value de la conversión.
    Por mi entendimiento, si p fueron una dirección de hardware que podría desencadenar algún tipo de acción cuando se leen, pero no fueron declarados volatile, la declaración de *p; no sería necesario, pero sería permitido, de hecho, leer esa dirección; la declaración &(*p);, sin embargo, estaría prohibido de hacerlo. Si *p fueron volatile, la lectura sería necesario. En cualquier caso, si el puntero no es válido, no puedo ver cómo la primera declaración de no ser un Comportamiento Indefinido, pero también no puedo ver por qué la segunda declaración.
    «.E2 convierte en un r-value, » – Uh, no, no no

    OriginalEl autor GManNickG

  2. 26

    Obviamente indefinido significa que es no se define, pero a veces puede ser predecible. La información que estoy a punto de proporcionar nunca debe ser invocado por código de trabajo, ya que sin duda no está garantizado, pero puede ser útil cuando la depuración.

    Se podría pensar que llama a una función en un puntero a un objeto va a eliminar el puntero y la causa de la UB. En la práctica, si la función no es virtual, el compilador se han convertido en una llanura llamada a la función pasando el puntero como el primer parámetro este, sin pasar por la eliminación de referencias y la creación de una bomba de tiempo para la llamada de la función miembro. Si la función miembro no hace referencia a cualquier miembro de variables o funciones virtuales, que podría tener éxito sin error. Recuerde que las caídas en el universo de «undefined»!

    De Microsoft MFC función GetSafeHwnd en realidad se basa en este comportamiento. No sé lo que fueron el hábito de fumar.

    Si usted está llamando a una función virtual, el puntero debe estar eliminan las referencias para llegar a la vtable, y para asegurarse de que usted va a obtener de la UB (probablemente un accidente, pero recuerde que no hay garantías).

    GetSafeHwnd hace primero una !esta comprobación y si es true, devuelve NULL. A continuación, comienza una SEH marco y elimina las referencias el puntero. si no hay Memoria de Infracción de Acceso (0xc0000005) este es capturado y se devuelve NULL llamadas 🙂 otra Cosa que el HWND es devuelto.
    ha sido todo un par de años desde que miré el código para GetSafeHwnd, es posible que haya mejorado desde entonces. Y no hay que olvidar que se tiene conocimiento de información privilegiada en el compilador de funcionamiento!
    Estoy declarando un ejemplo de aplicación que tiene el mismo efecto, ¿qué es ser de ingeniería inversa mediante un depurador 🙂
    «ellos tienen conocimiento de información privilegiada en el compilador de funcionamiento!» – la causa de la eterna dificultad para proyectos como MinGW que el intento de permitir a g++ para compilar código que llama a la API de Windows
    Creo que todos estamos de acuerdo en que esto es injusto. Y por eso, también creo que hay una ley acerca de la compatibilidad que hace que sea un poco ilegal para mantenerlo así.

    OriginalEl autor Mark Ransom

Dejar respuesta

Please enter your comment!
Please enter your name here