2012-08-17 13 views
25

¿No es un puntero solo una dirección? ¿O me estoy perdiendo algo?¿Por qué el tamaño de un puntero a una función es diferente del tamaño de un puntero a una función miembro?

He probado con varios tipos de punteros:

  • punteros a las variables es la misma (8B en mi plataforma)
  • punteros a funciones son del mismo tamaño, como punteros a las variables (8B de nuevo)
  • punteros a funciones con diferentes parámetros - siendo el mismo (8B)

PERO punteros a funciones miembro son más grandes - 16B en mi plataforma.

tres cosas:

  1. Por qué son punteros a funciones miembro más grande? ¿Qué más información necesitan?
  2. Por lo que yo sé, el estándar no dice nada acerca del tamaño de un puntero, excepto que void* debe ser capaz de "contener" a cualquier tipo de puntero. En otras palabras, cualquier puntero debe poder convertirse a void*, ¿verdad? Si es así, ¿por qué sizeof(void*) es 8, mientras que sizeof un puntero a la función de miembro es 16?
  3. ¿Hay algún otro ejemplo para los punteros, que tienen diferentes tamaños (es decir, para estándar plataformas, no algunas raras y especiales)?
+1

Esencialmente, el estándar no requiere punteros de datos, punteros de función y punteros de función de miembro para que todos tengan el mismo tamaño. Si quiere saber por qué no tienen el mismo tamaño en su plataforma, tendrá que preguntarle a los responsables de su compilador de C++. http://www.parashift.com/c++-faq/cant-cvt-memfnptr-to-voidptr.html http://www.parashift.com/c++-faq/cant-cvt-fnptr-to-voidptr.html – Cubic

+0

@KirilKirov no hay problema. No siempre estoy siendo sarcástico :) –

Respuesta

26

EDITAR: me he dado cuenta que todavía estoy recibiendo votos en este mes más tarde, a pesar de que mi respuesta original es mala y engañosa (Ni siquiera puedo recordar lo que estaba pensando en ese momento, y se ¡no tiene mucho sentido!), así que pensé en intentar aclarar la situación, ya que las personas todavía deben llegar aquí a través de la búsqueda.

En la situación más normal, se puede casi pensar en

struct A { int i; int foo() { return i; } }; 
A a; a.foo(); 

como

struct A { int i; }; 
int A_foo(A* this) { return this->i; }; 
A a; A_foo(&a); 

(empezando a parecerse a C, ¿verdad?) Así que se podría pensar el puntero &A::foo simplemente haría ser lo mismo que un puntero de función normal. Pero hay un par de complicaciones: herencia múltiple y funciones virtuales.

Así que imaginemos que tenemos:

struct A {int a;}; 
struct B {int b;}; 
struct C : A, B {int c;}; 

Podría ser presentado como esto:

Multiple inheritance

Como se puede ver, si se quiere apuntar al objeto con una A* o a C*, apunte al inicio, pero si quiere apuntarlo con B*, tiene que apuntar a algún punto intermedio.Entonces, si C hereda alguna función de miembro de B y desea apuntar a ella, luego llame a la función en un C*, necesita saber cómo barajar el puntero this. Esa información necesita ser almacenada en alguna parte. Entonces se agrupa con el puntero a la función.

Ahora para cada clase que tenga virtual funciones, el compilador crea una lista de ellas llamada tabla virtual . A continuación, agrega un puntero adicional a esta tabla a la clase (vptr). Así, por esta estructura de clases:

struct A 
{ 
    int a; 
    virtual void foo(){}; 
}; 
struct B : A 
{ 
    int b; 
    virtual void foo(){}; 
    virtual void bar(){}; 
}; 

El compilador podría terminar haciendo de esta manera: enter image description here

así que un puntero de función miembro a una función virtual en realidad es necesario que haya un índice en la tabla virtual. Por lo tanto, un puntero de función miembro realmente necesita 1) posiblemente un puntero a la función, 2) posiblemente un ajuste del puntero this, y 3) posiblemente un índice vtable. Para ser coherente, cada puntero de función miembro debe ser capaz de todos estos. Así que eso es 8 bytes para el puntero, 4 bytes para el ajuste,bytes para el índice, para 16 bytes en total.

Creo que esto es algo que realmente varía mucho entre los compiladores, y hay muchas optimizaciones posibles. Probablemente ninguno realmente lo implementa de la manera que he descrito.

Para un lote de detalles, consulte this (vaya a "Implementaciones de los punteros de función de miembro").

+0

Me he tomado la libertad de insertar el texto del Estándar que creo que estabas buscando. Espero que esté bien. – jogojapan

+0

Tenga en cuenta que, en lo que respecta al estándar, un 'int *' puede ser más pequeño que un 'void *'; para algunas implementaciones C muy antiguas, lo era. Y todo lo que está garantizado para un 'void *' son los datos no miembros: no hay nada que garantice, por ejemplo, que un puntero a los datos de los miembros no sea más grande que 'void *' (aunque no puedo imaginar una implementación razonable donde podría ser). –

+1

Esto: va a implementarse como algo así como un puntero a un objeto obviamente no es correcto. El puntero del objeto se proporciona en el punto donde se llama al método. Pero creo que acabas de malinterpretar tus palabras. Simplemente lo volvería a formular para indicar que se requiere más información para soportar el comportamiento polimórfico. –

6

Básicamente porque necesitan soportar el comportamiento polimórfico. Vea un buen article por Raymond Chen.

0

Supongo que tiene algo que ver con el puntero this ... Es decir, cada función miembro también debe tener el puntero de la clase en la que se encuentran. El puntero hace que la función sea un poco más grande.

+3

No, adivinado mal ver mi comentario sobre la respuesta de Dirk –

+0

Esta respuesta es también alomg mal.. con Dirk Holsopple's –

+0

:) Pensé de la misma manera, weggo, pero no es cierto. Por ejemplo, porque inicializar ese puntero no tiene nada que ver con ningún objeto, sino solo con la declaración de clase. Eso me mostró que soy incorrecto. –

2

Algunas explicaciones se pueden encontrar aquí: The underlying representation of member function pointers

Aunque punteros a miembros se comportan como punteros ordinarios, detrás de las escenas de su representación es muy diferente. De hecho, un puntero a miembro generalmente consiste en una estructura que contiene hasta cuatro campos en ciertos casos. Esto se debe a que los punteros a los miembros tienen que admitir no solo funciones de miembro ordinarias, sino también funciones de miembros virtuales, funciones de miembros de objetos que tienen múltiples clases base y funciones de miembro de clases base virtuales. Por lo tanto, la función miembro más simple se puede representar como un conjunto de dos punteros: uno que contiene la dirección de memoria física de la función miembro, y un segundo puntero que contiene este puntero. Sin embargo, en casos como una función de miembro virtual, herencia múltiple y herencia virtual, el puntero al miembro debe almacenar información adicional. Por lo tanto, no puede enviar punteros a los miembros a punteros ordinarios ni puede lanzar con seguridad punteros a miembros de diferentes tipos. cita en bloque

0

Algunas de las principales razones para la representación de punteros a funciones miembro como {this, T (*f)()} son:

  • Tiene aplicación más simple en el compilador de implementar punteros a funciones miembro como T (*f)()

  • No implica generación de código de tiempo de ejecución ni contabilidad adicional

  • Se realiza razonablemente bien en comparación con T (*f)()

  • No hay suficiente demanda de los programadores de C++ para el tamaño de punteros a funciones miembro para que sea igual sizeof(void*)

  • Runtime generación de código durante la ejecución es de facto una tabú para el código C++ actualmente

Cuestiones relacionadas