2010-07-09 59 views
10

Con algunos antecedentes en las instrucciones de ensamblaje y los programas C, puedo visualizar cómo se vería una función compilada, pero es gracioso, nunca he pensado tan cuidadosamente cómo sería una clase C++ compilada.¿Cómo es una clase compilada de C++?

bash$ cat class.cpp 
#include<iostream> 
class Base 
{ 
    int i; 
    float f; 
}; 

bash$ g++ -c class.cpp 

me corrieron:

bash$objdump -d class.o 
bash$readelf -a class.o 

pero lo que consigue es difícil de entender para mí.

Podría alguien explicarme o sugerir algunos buenos puntos de partida.

+1

Se verá más o menos lo mismo que una estructura C. –

+0

¡Derecho !. Así que esto me llevó a pensar cómo se compilarían las estructuras, y me di cuenta de que tal vez no entiendo esta parte, para empezar. bash $ cat struct.cpp struct test { int i; flotador f; }; ¿Qué archivo de objeto y objeto deberá corresponder a este archivo contendrá? Entiendo que los archivos de objetos correspondientes a estructuras no se parecerán a las funciones en el sentido de que no hay instrucciones de ensamblaje. Gracias, – xyz

+0

en particular, ¿esta struct.cpp no ​​debería tener ni un texto ni una sección de datos en los archivos objeto? No sé si esta pregunta se inclina más hacia el formato de los archivos elf. – xyz

Respuesta

0

Prueba el

g ++ -S class.cpp

que le dará un archivo de ensamblaje 'class.s' (archivo de texto) que se puede leer con un editor de texto. Sin embargo, su código no hace nada (declarar que una clase no genera código por sí misma) por lo que no tendrá mucho en el archivo de ensamblaje.

2

ok. no hay nada especial con las clases compiladas. las clases compiladas incluso no existen. lo que existe son objetos que son pedazos planos de memoria con posibles rellenos entre campos? y el miembro independiente funciona en algún lugar del código que toma el puntero a un objeto como primer parámetro.

así objeto de la clase base debe ser algo

(* base_address): i (* base_address + sizeof (int)): f

que es posible tener rellenos entre los campos? pero eso es específico del hardware. basado en el modelo de memoria de procesadores.

también ... en la versión de depuración es posible detectar la descripción de la clase en los símbolos de depuración. pero eso es específico del compilador. debería buscar un programa que descargue símbolos de depuración para su compilador.

2

"Clases compiladas" significan "métodos compilados".

Un método es una función ordinaria con un parámetro adicional, generalmente se coloca en un registro (% ecx sobre todo creo, esto es, al menos, cierto para la mayoría de los compiladores de Windows que tienen para producir objetos COM utilizando __thiscall convención).

Por lo tanto, las clases de C++ no son terriblemente diferentes de un grupo de funciones ordinarias, a excepción de la manipulación de nombres y algo de magia en constructores/destructores para configurar vtables.

19

Las clases están (más o menos) construidas como estructuras normales. Los métodos son (más o menos ...) convertidos en funciones cuyo primer parámetro es "this". Las referencias a las variables de clase se realizan como un desplazamiento a "esto".

En cuanto a la herencia, vamos a citar las preguntas frecuentes de C++ LITE, que se refleja aquí http://www.parashift.com/c++-faq-lite/virtual-functions.html#faq-20.4. En este capítulo se muestra cómo las funciones virtuales son llamados en el hardware real (lo que hace la compilación en código máquina que


Vamos a trabajar un ejemplo de clase Base Supongamos que tiene 5 funciones virtuales:.. virt0() través virt4().

// Your original C++ source code 
class Base { 
public: 
    virtual arbitrary_return_type virt0(...arbitrary params...); 
    virtual arbitrary_return_type virt1(...arbitrary params...); 
    virtual arbitrary_return_type virt2(...arbitrary params...); 
    virtual arbitrary_return_type virt3(...arbitrary params...); 
    virtual arbitrary_return_type virt4(...arbitrary params...); 
    ... 
}; 

Paso # 1: el compilador genera una tabla estática que contiene 5 funciones triples, enterrando esa tabla en la memoria estática en alguna parte. Muchos compiladores (no todos) definen esta tabla al compilar el .cpp que define la primera función virtual no en línea de Base. Llamamos a esa tabla la tabla v; imaginemos que su nombre técnico es Base::__vtable. Si un puntero de función se ajusta en una palabra de máquina en la plataforma de hardware de destino, Base::__vtable terminará consumiendo 5 palabras ocultas de memoria. No 5 por instancia, no 5 por función; Sólo 5. podría ser algo como lo siguiente pseudo-código:

// Pseudo-code (not C++, not C) for a static table defined within file Base.cpp 

// Pretend FunctionPtr is a generic pointer to a generic member function 
// (Remember: this is pseudo-code, not C++ code) 
FunctionPtr Base::__vtable[5] = { 
    &Base::virt0, &Base::virt1, &Base::virt2, &Base::virt3, &Base::virt4 
}; 

Paso # 2: el compilador añade un puntero oculto (por lo general también una máquina de palabra) a cada objeto de la base de clase. Esto se llama v-pointer. Piense en este puntero oculto como un miembro de datos oculta, como si el compilador reescribe su clase a algo como esto:

// Your original C++ source code 
class Base { 
public: 
    ... 
    FunctionPtr* __vptr; ← supplied by the compiler, hidden from the programmer 
    ... 
}; 

Paso # 3: el compilador inicializa this->__vptr dentro de cada constructor. La idea es causar v Un triple de cada objeto para señalar en la tabla v de su clase, como si se añade la siguiente instrucción en init-la lista de cada constructor:

Base::Base(...arbitrary params...) 
    : __vptr(&Base::__vtable[0]) ← supplied by the compiler, hidden from the programmer 
    ... 
{ 
    ... 
} 

Ahora vamos a trabajar a cabo una clase derivada. Supongamos que su código C++ define la clase Der que hereda de la clase Base. El compilador repite los pasos 1 y 3 (pero no el 2). En el paso # 1, el compilador crea una tabla v oculta, manteniendo los mismos punteros de función que en Base::__vtable pero reemplazando las ranuras que corresponden a las anulaciones. Por ejemplo, si Der anula virt0() través virt2() y hereda los demás tal y como son, v-mesa de Der podría ser algo como (se simula Der no añade ninguna nueva virtuals):

// Pseudo-code (not C++, not C) for a static table defined within file Der.cpp 

// Pretend FunctionPtr is a generic pointer to a generic member function 
// (Remember: this is pseudo-code, not C++ code) 
FunctionPtr Der::__vtable[5] = { 
    &Der::virt0, &Der::virt1, &Der::virt2, &Base::virt3, &Base::virt4 
};          ^^^^----------^^^^---inherited as-is 

En el paso # 3, el compilador agrega una asignación de puntero similar al comienzo de cada uno de los constructores de Der. La idea es cambiar el v-puntero de cada objeto Der para que apunte a la tabla v de su clase. (Este no es un segundo v-puntero, es el mismo v-puntero que se definió en la clase base, Base; recuerde, el compilador no repite el paso 2 en la clase Der).

Finalmente, veamos cómo el compilador implementa una llamada a una función virtual. Su código podría tener este aspecto:

// Your original C++ code 
void mycode(Base* p) 
{ 
    p->virt3(); 
} 

El compilador tiene ni idea de si esto va a llamar o Base::virt3()Der::virt3() o tal vez el método de otra clase derivada que ni siquiera existe todavía virt3(). Solo se sabe con certeza que está llamando al virt3(), que es la función en la ranura n. ° 3 de la tabla v. Se vuelve a escribir esa llamada en algo como esto:

// Pseudo-code that the compiler generates from your C++ 

void mycode(Base* p) 
{ 
    p->__vptr[3](p); 
} 

os recomiendo que todos los desarrolladores C++ para leer las preguntas frecuentes. Puede tomar varias semanas (ya que es difícil de leer y de largo) pero le enseñará mucho sobre C++ y lo que se puede hacer con él.

1

La principal diferencia con respecto a la lectura de archivos de objeto C es que los nombres de los métodos C++ son mangled. Puede intentar usar la opción -C|--demangle con objdump.

0

Como una estructura C y un conjunto de funciones con un parámetro adicional que es un puntero a la estructura.

La manera más fácil de seguir lo que el compilador hizo quizás es compilar sin optimización, luego cargar el código en un depurador y pasarlo con el modo mixto de fuente/ensamblador.

Sin embargo, el objetivo del compilador es que no necesita saber esto (a menos que tal vez esté escribiendo un compilador).

Cuestiones relacionadas