virtuales programacion poo orientada objetos herencia funciones ejemplos ejemplo codigo clases c++

programacion - herencia c++



Llamando al método de clase a través del puntero de clase NULL (10)

Éste te explicará más que si uso palabras simples. Intenta compilarlo con cualquier compilador que quieras :) Pero ten en cuenta que es UB según los estándares.

#include <iostream> using namespace std; class Armor { public: void set(int data) { cout << "set("<<data<<")/n"; if(!this) { cout << "I am called on NULL object! I prefer to not crash!/n"; return; } this->data = data; //dereference it here } void get() { if(this) cout << "data = " << data << "/n"; else cout << "Trying to dereference null pointer detected!/n"; } int data; }; int main() { cout << "Hello World" << endl; Armor a; a.set(100); a.get(); Armor* ptr1 = &a; Armor* ptr2 = 0; ptr1->set(111); ptr2->set(222); ptr1->get(); ptr2->get(); return 0; }

Luego lea sobre __thiscall - y todos los comentarios anteriores.

Hello World set(100) data = 100 set(111) set(222) I am called on NULL object! I prefer to not crash! data = 111 Trying to dereference null pointer detected!

Esta pregunta ya tiene una respuesta aquí:

Tengo el siguiente fragmento de código:

class ABC{ public: int a; void print(){cout<<"hello"<<endl;} }; int main(){ ABC *ptr = NULL: ptr->print(); return 0; }

Se ejecuta con éxito. ¿Alguien puede explicarlo?


(No recuerdo dónde obtuve este conocimiento, así que podría estar completamente equivocado)

Debajo del capó, la mayoría de los compiladores transformarán su clase en algo como esto:

struct _ABC_data{ int a ; }; // table of member functions void _abc_print( _ABC_data* this );

donde _ABC_data es una estructura estilo C

y su llamada ptr->print(); se transformará en:

_abc_print( NULL)

que está bien durante la ejecución ya que no usas this arg.

ACTUALIZACIÓN: (Gracias al programador de Windows por el comentario correcto )
Tal código está bien solo para la CPU que lo ejecuta.
No hay absolutamente ninguna razón sensata para explotar esta característica de implementación . Y aquí está el porqué:

  1. Debido a que los estados estándar arrojan un comportamiento indefinido (¿alguien podría dar un enlace o al menos una referencia (capítulo N, par M ...)?)
  2. Si realmente necesita la capacidad de llamar a la función de miembro sin instancia, el uso de palabra clave estática le da eso con todas las comprobaciones de portabilidad y tiempo de compilación

Aunque no estoy seguro de si esta es la respuesta exacta, esta es mi comprensión. (Además, mi terminología para CPP es mala, ignóralo si es posible)

Para C ++, cuando se declara cualquier clase (es decir, aún no se ha creado un instante), las funciones se colocan en la sección .text del binario que se está creando. Cuando se crea un instante, las Funciones o los Métodos no se duplican. Es decir, cuando el compilador analiza el archivo CPP, reemplazará las llamadas de función para ptr->print() con la dirección apropiada definida en la sección .text.

Por lo tanto, todo lo que el compilador habría hecho es reemplazar la dirección adecuada en función del tipo de ptr para print función. (Lo que también significa algún control relacionado público / privado / herencia, etc.)

Hice lo siguiente para su código (llamado test12.cpp ):

EDITAR: Agregando algunos comentarios a ASM a continuación (Realmente no soy bueno en ASM, apenas puedo leerlo, solo lo suficiente como para entender algunas cosas básicas) Lo mejor sería leer este enlace de Wikilibro , que también he hecho: D En caso Alguien encuentra errores en ASW, por favor deje un comentario. Me gustaría arreglarlos y aprender más.

$ g++ test.cpp -S $ cat test.s ... // Following snippet is part of main function call movl $0, -8(%ebp) //this is for creating the NULL pointer ABC* ptr=NULL //It sets first 8 bytes on stack to ''0'' movl -8(%ebp), %eax //Load the ptr pointer into eax register movl %eax, (%esp) //Push the ptr on stack for using in function being called below //This is being done assuming that these elements would be used //in the print() function being called call _ZN3ABC5printE //Call to print function after pushing arguments (which are none) and //accesss pointer (ptr) on stack. ...

v ZN3ABC5printEv representa la definición global de la función definida en la class ABC :

... .LC0: //This declares a label named .LC0 .string "hello" // String "hello" which was passed in print() .section .text._ZN3ABC5printEv,"axG",@progbits,_ZN3ABC5printEv,comdat .align 2 .weak _ZN3ABC5printEv //Not sure, but something to do with name mangling .type _ZN3ABC5printEv, @function _ZN3ABC5printEv: //Label for function print() with mangled name //following is the function definition for print() function .LFB1401: //One more lavbel pushl %ebp //Save the ''last'' known working frame pointer .LCFI9: movl %esp, %ebp //Set frame (base pointer ebp) to current stack top (esp) .LCFI10: subl $8, %esp //Allocating 8 bytes space on stack .LCFI11: movl $.LC0, 4(%esp) //Pushing the string represented by label .LC0 in //in first 4 bytes of stack movl $_ZSt4cout, (%esp) //Something to do with "cout<<" statement call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp) movl %eax, (%esp) call _ZNSolsEPFRSoS_E //Probably call to some run time library for ''cout'' leave //end of print() function ret //returning control back to main() ...

Por lo tanto, incluso ((ABC *)0)->print(); funciona perfectamente bien


Como otros dijeron, es un comportamiento indefinido. En cuanto a la razón por la que parece funcionar, es porque no está tratando de acceder a la variable miembro a dentro de print() . Todas las instancias de la clase comparten la misma memoria para el código de print() por lo que this puntero no es necesario para acceder al método. Sin embargo, si intenta acceder a dentro del método, es más probable que obtenga una excepción de violación de acceso.


Esto funciona en todos los compiladores que he intentado (y lo he probado en muchos). Sí, está "indefinido" pero no está desreferenciando el puntero cuando llama a un miembro no virtual. Incluso puedes escribir código usando esta "función", aunque los puristas te gritarán y te llamarán nombres desagradables y demás.

Editar: Parece que hay algo de confusión aquí acerca de llamar funciones de miembro. NO está desreferenciando el puntero "this" cuando llama a un miembro no virtual. Simplemente está usando una sintaxis sofisticada para pasarlo como un parámetro. Esto es con todas las implementaciones que he visto, pero no está garantizado. Si no se implementó de esta manera, su código se ejecutará más lentamente. Una función de miembro es simplemente una función con un parámetro adicional semi oculto. ¡Eso es! fin de la historia. Dicho esto, puede haber algún compilador escrito por Clackus slack jaw software Co. que tenga un problema con esto, pero aún no lo he encontrado.


Expresión ptr->print(); se convertirá implícitamente en (*ptr).print(); de acuerdo con el estándar C ++ (5.2.5 / 3). Y desreferenciar el puntero nulo conduce a un comportamiento indefinido. Es fortuito que el código en cuestión funcione sin errores en su caso. No deberías confiar en eso.

5.2.5 / 3:

Si E1 tiene el tipo "puntero a clase X", entonces la expresión E1-> E2 se convierte a la forma equivalente (* (E1)). E2; el resto de 5.2.5 abordará solo la primera opción (punto) 59). Abreviatura de expresión de objeto. id-expresión como E1.E2, entonces las propiedades type y lvalue de esta expresión se determinan de la siguiente manera. En el resto de 5.2.5, cq representa const o la ausencia de const; vq representa ya sea volátil o la ausencia de volátiles. cv representa un conjunto arbitrario de cv-calificadores, como se define en 3.9.3.


La mayoría de las respuestas dicen que el comportamiento indefinido puede incluir "aparecer" para trabajar, y tienen razón.

La respuesta de Alexander Malakhov dio detalles de implementación que son comunes y explica por qué su situación pareció funcionar, pero hizo una ligera declaración equivocada. Escribió "que está bien durante la ejecución ya que no usas esta arg", sino que significa "que parecía estar bien durante la ejecución ya que no usas esta arg".

Pero ten en cuenta que tu código sigue siendo un comportamiento indefinido. Imprimió lo que quería Y transfirió el saldo de su cuenta bancaria a la mía. Te lo agradezco.

(El estilo SO dice que esto debería ser un comentario, pero es demasiado largo. Lo hice CW).


Llamar a las funciones miembro utilizando un puntero que no apunta a un objeto válido da como resultado un comportamiento indefinido . Cualquier cosa podría pasar. Podría correr; podría colapsar.

En este caso, parece funcionar porque this puntero, que no apunta a un objeto válido, no se usa en la print .


Lleva a un comportamiento indefinido. Puse un poco de trabajo para explicar por qué. :) Pero esa es una respuesta más técnica.

Básicamente, un comportamiento indefinido significa que ya no se garantiza nada sobre la ejecución del programa; C ++ simplemente no tiene nada que decir. Podría funcionar exactamente como lo desee, o podría colapsar miserablemente, o podría hacer ambas cosas al azar.

Así que parece que trabajar es un excelente resultado de un comportamiento indefinido, que es lo que estás viendo. La razón práctica por la cual, en su implementación (y honestamente, en cada implementación), this puntero (la dirección de la instancia que se invoca) no se está utilizando en absoluto en su función. Dicho esto, si intenta utilizar this puntero (por ejemplo, accediendo a una variable miembro), es probable que se bloquee.

Recuerde, el párrafo anterior es algo específico para su implementación y su comportamiento actual. Es solo una suposición y algo en lo que no puedes confiar.


probablemente se ejecute porque su puntero de clase no está utilizando ninguna variable miembro en la función de impresión ... Si en la función de impresión intenta acceder a, no se ejecutará ... ya que el puntero de clase no inicializado no puede tener una variable de miembro inicializada ...