2009-06-02 20 views
30

Por ejemplo, decir que tengo una temperatura de clase:¿Cómo se determina el tamaño de un objeto en C++?

class Temp 
{ 
    public: 
     int function1(int foo) { return 1; } 
     void function2(int bar) { foobar = bar; } 

    private: 
     int foobar; 
}; 

Cuando creo un objeto de temperatura clase, ¿cómo puedo calcular la cantidad de espacio que necesita, y cómo se representa en la memoria (por ejemplo, | 4 bytes para foobar | 8 bytes para function1 | etc |)

+1

Esta pregunta puede ser un poco más descriptiva con un título como "¿Cómo se determina el tamaño de un objeto en C++?". –

+0

Here es un excelente libro sobre el tema. –

Respuesta

54

Para una aproximación de primer orden, el tamaño de un objeto es la suma de los tamaños de los miembros de sus datos constituyentes. Puedes estar seguro de que nunca será más pequeño que esto.

Más precisamente, el compilador tiene derecho a insertar espacio de relleno entre los miembros de datos para garantizar que cada miembro de datos cumpla con los requisitos de alineación de la plataforma. Algunas plataformas son muy estrictas con respecto a la alineación, mientras que otras (x86) son más indulgentes, pero tendrán un rendimiento significativamente mejor con la alineación adecuada. Entonces, incluso la configuración de optimización del compilador puede afectar el tamaño del objeto.

La herencia y las funciones virtuales añaden una complicación adicional. Como han dicho otros, las funciones miembro de su clase no ocupan el espacio "por objeto", pero la existencia de funciones virtuales en la interfaz de esa clase generalmente implica la existencia de una tabla virtual, esencialmente una tabla de búsqueda de indicadores de función utilizados para resolver dinámicamente la implementación de la función adecuada para llamar en tiempo de ejecución. La tabla virtual (vtbl) se accede generalmente a través de un puntero almacenado en cada objeto.

Los objetos de clase derivados también incluyen todos los miembros de datos de sus clases base.

Finalmente, los especificadores de acceso (público, privado, protegido) otorgan al compilador cierto margen de maniobra con el embalaje de los miembros de datos.

La respuesta corta es que sizeof (myObj) o sizeof (MyClass) siempre le dirá el tamaño correcto de un objeto, pero su resultado no siempre es fácil de predecir.

15
sizeof(Temp) 

le dará el tamaño. Lo más probable es que sea de 4 bytes (dado un montón de suposiciones) y eso es solo para el int. Las funciones no ocupan ningún espacio por objeto, se compilan una vez y el compilador las vincula cada vez que se utilizan.

Es imposible decir exactamente cuál es el diseño del objeto, sin embargo, el estándar no define la representación binaria de los objetos.

Hay algunas cosas a tener en cuenta con representaciones binarias, como que no son necesariamente la suma de los bytes de los miembros de datos, debido a cosas como structure padding

+2

y también debido a cosas como punteros de tabla virtual. – jrharshath

+0

@ harshath.jr: sí, pero no hay funciones virtuales en la clase que se muestra, por lo que no hay ninguna tabla virtual – newacct

0

This pueden ayudar.

Además, las funciones de clase se representan como cualquier otra función. La única magia que C++ tiene para la función es modificar los nombres de las funciones para identificar de manera única una función específica con un conjunto específico de parámetros dentro de una clase específica.

4

Las funciones de miembro no representan el tamaño de los objetos de una clase en particular. El tamaño del objeto depende solo de las variables miembro. En el caso de las clases que contienen funciones virtuales, el VPTR se agrega al diseño del objeto. Entonces, el tamaño de los objetos es básicamente el tamaño de las variables miembro + el tamaño de los VPTR. A veces, esto puede no ser cierto, ya que los compiladores intentan localizar variables miembro en el límite DWORD.

8

Si desea información detallada acerca de cómo se representan los objetos en la memoria en tiempo de ejecución, la especificación ABI (Application Binary Interface) es el lugar para buscar. Tendrá que buscar determinar qué ABI implementa su compilador; por ejemplo, las versiones 3.2 y posteriores de GCC implementan el Itanium C++ ABI.

6

Los métodos pertenecen a la clase, no a ningún objeto instanciado en particular.

A menos que haya métodos virtuales, el tamaño de un objeto es la suma del tamaño de sus miembros no estáticos, más el relleno opcional entre los miembros para la alineación. Los miembros probablemente se distribuirán secuencialmente en la memoria, pero la especificación no garantiza el orden entre secciones con diferentes especificaciones de acceso, ni ordenamiento relativo al diseño de las superclases.

Con los métodos virtuales presentes, puede haber espacio adicional para vtable y otra información RTTI.

En la mayoría de las plataformas, el código ejecutable va en la sección de solo lectura .text (o nombre similar) del ejecutable o biblioteca, y nunca se copia en ninguna parte. Cuando class Temp tiene un método public: int function1(int), los metadatos Temp pueden tener un puntero a una función _ZN4Temp9function1Ei (el nombre destruido puede ser diferente según el compilador) para la implementación real, pero ciertamente nunca contendría el código ejecutable incrustado.

1

Si desea examinar el diseño de una estructura particular, la macro offsetof(s,member) también puede ser útil. Se da a conocer a qué distancia de la dirección base de una estructura de un miembro en particular vive:

struct foo { 
    char *a; 
    int b; 
}; 

// Print placement of foo's members 
void printFoo() { 
    printf("foo->a is %zu bytes into a foo\n", offsetof(struct foo, a)); 
    printf("foo->b is %zu bytes into a foo\n", offsetof(struct foo, b)); 
} 

int main() { 
    printFoo(); 
    return 0; 
} 

imprimiría en una típica máquina de 32 bits:

foo->a is 0 bytes into a foo 
foo->b is 4 bytes into a foo 

Mientras que en una típica máquina de 64 bits, imprimiría

foo->a is 0 bytes into a foo 
foo->b is 8 bytes into a foo 
-1

El tamaño de un objeto de una clase es igual a la suma de los tamaños de todos los miembros de datos de esa clase. Por ejemplo, si tengo una clase

class student 
{ 
private: 
    char name[20]; 
    int rollno, admno; 
    float marks; 

public: 
    float tmarks, percentage; 

    void getdata(); 

    void putdata(); 
}; 

Ahora, si hago un objeto de esta clase, digo s1, entonces el tamaño de este objeto será de 36 bytes:

[20(name)+2(rollno)+2(admno)+4(marks)+4(tmarks)+4(percentage)] 
+5

Bienvenido a Stackoverflow. Su respuesta tiene el mismo contenido que, por ejemplo, eso por Drew Hall, excepto que omites partes esenciales, como sobre el relleno y sobre el efecto del polimorfismo. Si responde a una pregunta ya respondida, asegúrese de que su respuesta mejora las existentes. – jogojapan

0

Hay una llamada de utilidad pahole (para 'Poke-A-HOLE') que está destinado nominalmente a estudiar cómo se rellenan los diseños de objetos, pero es ideal para visualizar el tamaño y el diseño de los objetos en general.

5

Siempre me he preguntado este tipo de cosas, así que decidí dar una respuesta completa. Se trata de lo que cabría esperar, y es predecible (yay)! Por lo tanto, con la información a continuación, debe ser capaz de predecir el tamaño de una clase.

Uso de Visual Studio Community 2017 (Version 15.2), en modo de lanzamiento, con todas las optimizaciones discapacitado y RTTI (Run-time Type Information) fuera, que han determinado lo siguiente:


respuesta era corto:

En primer lugar:

  • En el bit 32 (x86), <size of pointer> == 4 bytes
  • En el bit 64 (x64), <size of pointer> == 8 bytes
  • Cuando digo "la herencia de clases virtual", es decir por ejemplo: class ChildClass: virtual public ParentClass

Ahora, mis conclusiones son que:

  • clases vacías son 1 byte
  • herencia de una clase vacía es aún 1 byte
  • clases vacías con funciones todavía de 1 byte (?! ver Nota explicación a continuación)
  • herencia de una clase vacía con una función sigue siendo 1 byte
  • añadir una variable a una clase vacía se <size of variable> bytes
  • heredar una clase con una variable y añadiendo otra variable es <size of variables> bytes
  • que heredan una clase y imperiosas su función añade un vtable (explicación adicional proporcionada en Conclusiones sección) y <size of pointer> bytes
  • simplemente declarando una virtua función l también añade un vtable, por lo que es <size of pointer> bytes
  • herencia de clases virtual de una clase vacía (con o sin una función miembro) también añade un vtable, y hace que la clase <size of pointer> bytes
  • herencia de clases virtual de un no vacía clase también añade un vtable, pero se hace un poco complicado: se añade<size of pointer> bytes al total, envolviendo todas las variables miembro en tantos incrementos <size of pointer> bytes como sea necesario para cubrir <total size of member variables> - sí, has leído bien. .. (ver mi conjetura sobre lo que está pasando en Conclusiones ...)

Nota que incluso intenté que la función() incluyera texto, creara una instancia de la clase y llamara a la función; no cambia el tamaño de la clase de función (¡no es una optimización)! Me sorprendió un poco, pero en realidad tiene sentido: las funciones de los miembros no cambian, por lo que se pueden almacenar de forma externa a la clase misma.

Conclusiones:

  • clases vacías son de 1 byte, ya que es el mínimo requerido para que tenga una presencia en la memoria. Sin embargo, una vez que se agregan los datos de datos o vtable, comience a contar a 0 bytes.
  • Agregar una función miembro (no virtual) no hace nada al tamaño, porque la función miembro se almacena externamente.
  • Declarar que una función miembro es virtual (¡incluso si la clase no está anulada!) O anular una función miembro en una clase secundaria agrega lo que se llama "vtable" or "virtual function table", que permite Dynamic Dispatch (que es realmente genial de usar y Recomiendo usarlo). Este vtable consume <size of pointer> bytes, agregando <size of pointer> bytes a dicha clase. Este vtable solo puede existir una vez por clase (ya sea que lo haga o no), por supuesto.
  • Agregar una variable miembro aumenta el tamaño de la clase por esa variable miembro, independientemente de si dicha variable miembro está en la clase principal o secundaria (aunque la clase principal sigue siendo de su propio tamaño, por supuesto).
  • La herencia de clase virtual es la única parte que se complica ... Entonces ... Creo que lo que sucede después de un poco de experimentación es: el tamaño de la clase en realidad aumenta en <size of pointer> bytes a la vez, incluso si no lo hace t necesidad de consumir esta cantidad de memoria, supongo que porque es la adición de un "bloque ayudante" vtable para cada <size of pointer> bytes de memoria o algo ...

respuesta larga:

Decidí todo esto usando este código:

#include <iostream> 

using namespace std; 

class TestA 
{ 

}; 

class TestB: public TestA 
{ 

}; 

class TestC: virtual public TestA 
{ 

}; 

class TestD 
{ 
    public: 
     int i; 
}; 

class TestE: public TestD 
{ 
    public: 
     int j; 
}; 

class TestF: virtual public TestD 
{ 
    public: 
     int j; 
}; 

class TestG 
{ 
    public: 
     void function() 
     { 

     } 
}; 

class TestH: public TestG 
{ 
    public: 
     void function() 
     { 

     } 
}; 

class TestI: virtual public TestG 
{ 
    public: 
     void function() 
     { 

     } 
}; 

class TestJ 
{ 
    public: 
     virtual void function() 
     { 

     } 
}; 

class TestK: public TestJ 
{ 
    public: 
     void function() override 
     { 

     } 
}; 

class TestL: virtual public TestJ 
{ 
    public: 
     void function() override 
     { 

     } 
}; 

void main() 
{ 
    cout << "int:\t\t" << sizeof(int) << "\n"; 
    cout << "TestA:\t\t" << sizeof(TestA) << "\t(empty class)\n"; 
    cout << "TestB:\t\t" << sizeof(TestB) << "\t(inheriting empty class)\n"; 
    cout << "TestC:\t\t" << sizeof(TestC) << "\t(virtual inheriting empty class)\n"; 
    cout << "TestD:\t\t" << sizeof(TestD) << "\t(int class)\n"; 
    cout << "TestE:\t\t" << sizeof(TestE) << "\t(inheriting int + int class)\n"; 
    cout << "TestF:\t\t" << sizeof(TestF) << "\t(virtual inheriting int + int class)\n"; 
    cout << "TestG:\t\t" << sizeof(TestG) << "\t(function class)\n"; 
    cout << "TestH:\t\t" << sizeof(TestH) << "\t(inheriting function class)\n"; 
    cout << "TestI:\t\t" << sizeof(TestI) << "\t(virtual inheriting function class)\n"; 
    cout << "TestJ:\t\t" << sizeof(TestJ) << "\t(virtual function class)\n"; 
    cout << "TestK:\t\t" << sizeof(TestK) << "\t(inheriting overriding function class)\n"; 
    cout << "TestL:\t\t" << sizeof(TestL) << "\t(virtual inheriting overriding function class)\n"; 

    cout << "\n"; 
    system("pause"); 
} 

de salida:

32 (x 86) bits:

int:   4 
TestA:   1  (empty class) 
TestB:   1  (inheriting empty class) 
TestC:   4  (virtual inheriting empty class) 
TestD:   4  (int class) 
TestE:   8  (inheriting int + int class) 
TestF:   12  (virtual inheriting int + int class) 
TestG:   1  (function class) 
TestH:   1  (inheriting function class) 
TestI:   4  (virtual inheriting function class) 
TestJ:   4  (virtual function class) 
TestK:   4  (inheriting overriding function class) 
TestL:   8  (virtual inheriting overriding function class) 

64 (x64) bits:

int:   4 
TestA:   1  (empty class) 
TestB:   1  (inheriting empty class) 
TestC:   8  (virtual inheriting empty class) 
TestD:   4  (int class) 
TestE:   8  (inheriting int + int class) 
TestF:   24  (virtual inheriting int + int class) 
TestG:   1  (function class) 
TestH:   1  (inheriting function class) 
TestI:   8  (virtual inheriting function class) 
TestJ:   8  (virtual function class) 
TestK:   8  (inheriting overriding function class) 
TestL:   16  (virtual inheriting overriding function class) 

Si desea información sobre herencia múltiple, ¡averígüelo usted mismo! -.-

Cuestiones relacionadas