2010-03-11 23 views
7

Cuando creamos un objeto de una clase, ¿cómo se ve el mapa de memoria? Estoy más interesado en cómo el objeto llama a las funciones de miembros no virtuales. ¿El compilador crea una tabla como vtable que se comparte entre todos los objetos?C++ class object memory map

class A 
{ 
public: 
    void f0() {} 
    int int_in_b1; 
}; 

A * a = new A; 

¿Cuál será el mapa de memoria de un?

+4

Recomiendo 'Dentro del Modelo de Objetos C++' de Stanley Lippman si quiere cómo se pueden modelar objetos C++ (digo que puedo porque hay múltiples formas de implementar C++ internos). –

+0

Si corrige su código, ¿por qué no ejecuta su compilador con salida de ensamblador y ve lo que genera? –

Respuesta

12

Usted puede imaginar este código:

struct A { 
    void f() {} 
    int int_in_b1; 
}; 

int main() { 
    A a; 
    a.f(); 
    return 0; 
} 

está transformando en algo así como:

struct A { 
    int int_in_b1; 
}; 
void A__f(A* const this) {} 

int main() { 
    A a; 
    A__f(&a); 
    return 0; 
} 

Calling f es directo porque no es virtual. (Y a veces para las llamadas virtuales, el envío virtual se puede evitar si se conoce el tipo dinámico del objeto, como está aquí.)


Un ejemplo ya que, o bien darle una idea acerca de cómo funciona el trabajo virtual o terriblemente se confunden:

struct B { 
    virtual void foo() { puts(__func__); } 
}; 
struct D : B { 
    virtual void foo() { puts(__func__); } 
}; 

int main() { 
    B* a[] = { new B(), new D() }; 
    a[0]->foo(); 
    a[1]->foo(); 
    return 0; 
} 

ser algo así como:

void B_foo(void) { puts(__func__); } 
void D_foo(void) { puts(__func__); } 

struct B_VT { 
    void (*foo)(void); 
} 
B_vtable = { B_foo }, 
D_vtable = { D_foo }; 

typedef struct B { 
    struct B_VT* vt; 
} B; 
B* new_B(void) { 
    B* p = malloc(sizeof(B)); 
    p->vt = &B_vtable; 
    return p; 
} 

typedef struct D { 
    struct B_VT* vt; 
} D; 
D* new_D(void) { 
    D* p = malloc(sizeof(D)); 
    p->vt = &D_vtable; 
    return p; 
} 

int main() { 
    B* a[] = {new_B(), new_D()}; 
    a[0]->vt->foo(); 
    a[1]->vt->foo(); 
    return 0; 
} 

Cada objeto tiene solamente un puntero vtable y puede agregar muchos métodos virtuales a la clase sin afectar el tamaño del objeto. (El vtable crece, pero se almacena una vez por clase y no tiene una sobrecarga de tamaño significativa.) Tenga en cuenta que simplifiqué muchos detalles en este ejemplo, pero does work: los destructores no están direccionados (que también deberían ser virtuales aquí), pierde la memoria, y los valores __func__ serán ligeramente diferentes (son generados por el compilador para el nombre de la función actual), entre otros.

+0

El segundo ejemplo tiene algunas semanas de antigüedad que escribí, y ahora veo que olvidé agregar punteros * this *, aunque no se usen. Si no ves cómo agregarlos, solo házmelo saber y puedo editar; de lo contrario, lo mantendré igual que el código compilado en el enlace del teclado numérico. –

3

Reconoce que el lenguaje C++ no especifica ni ordena todo lo relacionado con el diseño de la memoria para los objetos. Dicho eso, la mayoría de los compiladores lo hacen más o menos igual.

En su ejemplo, los objetos de tipo A solo requieren suficiente memoria para contener un int. Como no tiene funciones virtuales, no necesita vtable. Si el miembro f0 se hubiera declarado virtual, entonces los objetos de tipo A normalmente comenzarían con un puntero a la clase A vtable (compartida por todos los objetos de tipo A) seguidos por el miembro int.

A su vez, el vtable tiene un puntero a cada función virtual, definida, heredada o anulada. Llamar a una función virtual para un objeto consiste en seguir el puntero al vtable desde el objeto, luego usar un desplazamiento fijo en el vtable (determinado en tiempo de compilación para cada función virtual) para encontrar la dirección de la función a llamar.

+0

Sé cómo funciona una vtable. Estoy interesado en cómo el compilador trata con la función no virtual. ¿Hay una mesa separada para ellos también? – Bruce

+1

@ Peter: las funciones no influyen en el tamaño de la clase y no afectan al diseño. Las funciones son como cualquier otra función que escriba, residen en la memoria en algún lugar esperando ser llamadas. Lo único sobre las funciones miembro es que tienen un puntero 'this' implícito que no ves. – GManNickG

+0

Entonces cuando escribo a.f0() ¿cómo obtiene el compilador la dirección de f0()? – Bruce

0
class A 
{ 
public: 
    void f0() {} 
    void f1(int x) {int_in_b1 = x; } 
    int int_in_b1; 
}; 

A *a = new A(); 

se implementa internamente (representado) de esta manera: (nombre de la función son en realidad mutilada)

struct A 
{ 
    int int_in_b1; 
}; 

void Class_A__constructor(struct a*) {} // default constructor 
void Class_A__f0(struct a*) {} 
void Class_A__f1(struct a*, int x) {a->int_in_b1 = x;} 

// new is translated like this: (inline) 
void* new() { 
    void* addr = malloc(sizeof(struc a)); 
    Class_A__constructor(addr); 
    return addr; 
} 

Se puede comprobar haciendo un comando "nm" en el archivo de objeto (resultado con el nombre destrozado)

+0

Copió el error 'A a = new A();' de la pregunta. –

+0

@Roger: Gracias, no me di cuenta de – Phong

1

funciones no se almacenan en base a lo que están en la clase.

por lo general el compilador simplemente tratar cualquier función miembro al igual que cualquier otra función, excepto agrega un argumento para el puntero 'this'. que se pasa automáticamente a la función cuando la llamas en función de la dirección del objeto al que se llama.

todas las funciones, miembro estático, miembro o incluso virtual se almacenan en la memoria de la misma manera, todas son solo funciones.

cuando el compilador crea el código que más o menos codifica donde va a la memoria, luego el enlazador revisa su código y reemplaza el comando "llame a la función con este nombre" con "invoque la función en esta dirección codificada "