2010-05-25 13 views
5

Esto es más una curiosidad que otra cosa ...¿Dónde almacena el compilador los métodos para las clases en C++?

Supongamos que tengo una clase de C++ gatito de la siguiente manera:

class Kitty 
{ 
    void Meow() 
    { 
     //Do stuff 
    } 
} 

¿El lugar compilador el código para maullido() en todos los casos de Kitty?

Obviamente, repetir el mismo código en todas partes requiere más memoria. Pero, por otro lado, la bifurcación a una ubicación relativa en la memoria cercana requiere menos instrucciones de ensamblaje que la bifurcación a una ubicación absoluta en la memoria en los procesadores modernos, por lo que es potencialmente más rápido.

Supongo que se trata de un detalle de implementación, por lo que los diferentes compiladores pueden tener un funcionamiento diferente.

Tenga en cuenta que no estoy considerando métodos estáticos o virtuales aquí.

+0

Prepárese para la avalancha de respuestas a una pregunta comúnmente conocida (aunque +1, * es * una buena pregunta :)) .. –

+0

Para aclarar, no estoy interesado en la inclusión. Soy consciente de cómo funciona eso. – Mashmagar

Respuesta

3

Creo que la forma estándar de los métodos de ejemplo debe implementarse como cualquier método estático, solo una vez, pero teniendo el puntero this pasado en un registro específico o en la pila para realizar la llamada.

1

No, el compilador solo genera el código para Meow una vez y cada instancia de Kitty usa eso siempre que el miembro se compiló fuera de línea. Si el compilador puede y elige alinear la función, entonces se duplica en cada punto de uso (en lugar de con cada instancia de Kitty).

4

En la implementación habitual, solo hay una copia de una función dada. La asociación entre el código y los datos para una instancia de objeto determinada se establece pasando un parámetro oculto (referido a this en la función) que es un puntero a la instancia del objeto (y sus datos).

Para las funciones virtuales, las cosas se ponen un poco más complicado: cada clase consigue un vtable que contiene un conjunto de punteros a las funciones virtuales, y cada objeto obtiene un puntero a la viable para su clase. Las funciones virtuales se invocan encontrando el puntero vtable, mirando el desplazamiento correcto e invocando la función apuntada por ese puntero.

+0

Si lo entiendo correctamente, bajo el capó está haciendo lo que Python hace explícitamente en cada función miembro, requiriendo la referencia a la instancia. ¿Sí? – Mashmagar

+0

Independientemente de dónde utilice explícitamente el puntero 'this' al acceder a una función miembro o variable miembro, siempre se usa. En el caso de las funciones miembro, eso significa pasar 'this' a la función miembro como su primer parámetro (invisible). Si se trata de una función virtual, la tabla virtual complica las cosas, pero esencialmente, siempre está pasando el puntero 'this' a las funciones miembro como su primer parámetro. Es por eso que ciertas sobrecargas de operador (como '<<') tienen que ser funciones de amigo en lugar de funciones de miembro. –

+0

@Mashmagar: sí, los dos son bastante similares. –

2

No, esta no es la manera en que se hace.
Los métodos que no son virtual son exactamente iguales que cualquier otra función pero con un argumento adicional para el puntero this.

Los métodos que son virtual son invoked using a v-table. la tabla v es una lista de indicadores de funciones que se almacenan al lado de los datos de los objetos. En cierto sentido, esto está más cerca de lo que describes, pero aún así, el cuerpo de la función es siempre el mismo para todas las instancias del objeto.
Esto se puede demostrar si tiene una variable static en el método. La variable estática va a ser la misma para los métodos invocados desde diferentes instancias.

+0

En realidad, la mayoría de las implementaciones colocan un * puntero * al vtable en el objeto y no el vtable en sí. Este puntero a menudo se llama * vptr *. – fredoverflow

0

El compilador crea una entrada para cada clase (no objeto) dentro de su propia estructura de datos. Esta entrada para la clase contiene punteros a los métodos para esa clase.

Un objeto se representa en la memoria como un puntero a la clase padre y una colección de sus campos de instancia (ya que son diferentes para cada objeto). Cuando se llama a un método, el objeto sigue el puntero a su padre que luego sigue el puntero al método apropiado. También se suministra un puntero al objeto al método, que actúa como este puntero.

Los métodos virtuales son un poco más complicados, pero se hacen de forma similar.

Si quiere saber más, vea si puede tomar una clase de lenguajes de programación.

He aquí un pobre intento de arte ASCII para explicarlo:

obj      class 
+------------+   +----------+ 
| ptrToClass |----------->| method1 | ----------> toSomewhere(ptrToObj) 
|------------|   |----------| 
| field1  |   | method2 | ----------> toSomewhereElse(ptrToObj) 
+------------+   +----------+ 
2

Debido a que tiene la definición de Meow dentro de la definición de clase, Meow está implícita en línea.

inline es una sugerencia para el compilador para reemplazar la llamada con el contenido real de la función. Pero es solo una pista: el compilador puede elegir ignorar la sugerencia.

Si el compilador cumple con la sugerencia, cada llamada será reemplazada por el contenido de la función. Eso significa que el compilador generará el código cada vez que se llame al Meow en lugar de generar una llamada de función.

Si el compilador ignora la sugerencia, el compilador/enlazador hará los arreglos para que haya una única versión a la que se dirigirán todas las llamadas (porque está en línea, una estrategia clásica es que cada unidad de traducción que use la función obtener una copia por separado con instrucciones para el enlazador para mantener solo una versión).

Finalmente, pasemos a las explicaciones donde la función no está en línea. En este caso, es necesario que el codificador se asegure de que la definición aparezca exactamente en una unidad de traducción y que todas las llamadas se envíen a esta versión.

Cuestiones relacionadas