Par exemple, disons que j'ai une classe Temp:
class Temp
{
public:
int function1(int foo) { return 1; }
void function2(int bar) { foobar = bar; }
private:
int foobar;
};
Lorsque je crée un objet de classe Temp, comment puis-je calculer l'espace requis et comment est-il représenté en mémoire (par exemple | 4 octets pour foobar | 8 octets pour function1 | etc |
Pour une approximation de premier ordre, la taille d'un objet est la somme de la taille de ses données membres. Vous pouvez être sûr qu'il ne sera jamais plus petit que cela.
Plus précisément, le compilateur est autorisé à insérer un espace de remplissage entre les membres de données pour s’assurer que chaque membre de données répond aux exigences d’alignement de la plate-forme. Certaines plates-formes sont très strictes en matière d'alignement, tandis que d'autres (x86) sont plus tolérantes, mais fonctionneront nettement mieux avec un alignement correct. Ainsi, même le paramètre d'optimisation du compilateur peut affecter la taille de l'objet.
Les fonctions d'héritage et virtuelles ajoutent une complication supplémentaire. Comme d'autres l'ont déjà dit, les fonctions membres de votre classe ne prennent pas elles-mêmes l'espace "par objet", mais l'existence de fonctions virtuelles dans l'interface de cette classe implique généralement l'existence d'une table virtuelle, essentiellement une table de recherche de pointeurs de fonctions utilisés pour résoudre dynamiquement l'implémentation de la fonction appropriée à appeler au moment de l'exécution. La table virtuelle (vtbl) est généralement accessible via un pointeur stocké dans chaque objet.
Les objets de classe dérivés incluent également tous les membres de données de leurs classes de base.
Enfin, les spécificateurs d’accès (public, privé, protégé) accordent au compilateur une certaine marge de manœuvre avec l’emballage des membres de données.
La réponse courte est que sizeof (myObj) ou sizeof (MyClass) vous indiquera toujours la taille appropriée d'un objet, mais que son résultat n'est pas toujours facile à prédire.
sizeof(Temp)
vous donnera la taille. Très probablement, il s'agit de 4 octets (étant donné beaucoup d'hypothèses) et ce n'est que pour l'int. Les fonctions ne prennent pas de place par objet, elles sont compilées une fois et liées par le compilateur à chaque utilisation.
Il est impossible de dire exactement quelle est la disposition des objets. Toutefois, la norme ne définit pas la représentation binaire des objets.
Il faut tenir compte de certaines choses avec les représentations binaires, comme si elles ne correspondaient pas nécessairement à la somme des octets des membres de données, en raison de choses comme structure padding
Si vous souhaitez des informations détaillées sur la façon dont les objets sont représentés en mémoire au moment de l'exécution, la spécification ABI ( Application Binary Interface ) est l'endroit à utiliser. Vous devrez chercher à déterminer quel ABI votre compilateur implémente; Par exemple, les versions 3.2 et supérieures de GCC implémentent l'ABI Itanium C++ .
Je me suis toujours demandé ce genre de chose, alors j'ai décidé de fournir une réponse complète. Il s'agit de ce à quoi vous pouvez vous attendre, et c'est prévisible (oui)! Ainsi, avec les informations ci-dessous, vous devriez être capable de prédire la taille d'une classe.
À l'aide de Visual Studio Community 2017 (version 15.2), en mode de publication avec toutes les optimisations désactivées et RTTI ( Informations de type à l'exécution ) désactivées, j'ai déterminé les éléments suivants:
Réponse courte:
Tout d'abord:
<size of pointer> == 4
octets<size of pointer> == 8
octetsclass ChildClass: virtual public ParentClass
Maintenant, mes conclusions sont que:
<size of variable>
octets<size of variables>
octets<size of pointer>
octets<size of pointer>
octets<size of pointer>
octets<size of pointer>
octets au total, wrapping toutes les variables membres dans autant de <size of pointer>
octets incrémente tel quel nécessaire pour couvrir <total size of member variables>
- oui, vous avez bien lu ... (voyez ce que je pense de ce qui se passe dans Conclusions ...)Note que j'ai même essayé de demander à la fonction () d'exploiter du texte, de créer une instance de la classe et d'appeler la fonction; cela ne change pas la taille de la classe de fonctions (ce n'est pas une optimisation)! J'étais un peu surpris, mais cela a du sens: les fonctions membres ne changent pas, elles peuvent donc être stockées à l'extérieur de la classe.
Conclusions:
<size of pointer>
octets, en ajoutant <size of pointer>
octets à ladite classe. Cette table virtuelle ne peut exister qu'une fois par classe (que ce soit ou non), bien sûr.<size of pointer>
octets à la fois, même si elle n'a pas besoin de consomme autant de mémoire, je suppose, car cela ajoute un "bloc d'assistance" vtable pour chaque <size of pointer>
octets de mémoire ou quelque chose du genre ...Longue réponse:
J'ai déterminé tout cela en utilisant ce code:
#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");
}
Sortie:
32 (x86) 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 bits (x64):
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 vous voulez des informations sur l'héritage multiple, allez le découvrir vous-même! -.-
Les méthodes appartiennent à la classe et non à un objet instancié particulier.
Sauf s'il existe des méthodes virtuelles, la taille d'un objet correspond à la somme de la taille de ses membres non statiques, plus un remplissage optionnel entre les membres pour l'alignement. Les membres seront probablement disposés séquentiellement en mémoire, mais la spécification ne garantit pas un ordre entre sections avec des spécifications d'accès différentes, ni un ordre relatif à la disposition des superclasses.
Avec les méthodes virtuelles présentes, il peut y avoir un espace supplémentaire pris pour vtable et d’autres informations RTTI.
Sur la plupart des plates-formes, le code exécutable est inséré dans la section en lecture seule .text
(ou portant le même nom) de l'exécutable ou de la bibliothèque et n'est jamais copié nulle part. Lorsque class Temp
a une méthode public: int function1(int)
, les métadonnées Temp
peuvent avoir un pointeur sur une fonction _ZN4Temp9function1Ei
(le nom modifié peut être différent selon le compilateur) pour l'implémentation réelle, mais elle ne contiendra certainement jamais le code exécutable incorporé.
Les fonctions membres ne tiennent pas compte de la taille des objets d'une classe particulière. La taille de l'objet dépend uniquement des variables membres. Dans le cas de classes contenant des fonctions virtuelles, le VPTR est ajouté à la présentation de l'objet. La taille des objets est donc essentiellement la taille des variables membres + la taille des VPTR. Parfois, cela peut ne pas être vrai car les compilateurs essaient de localiser les variables de membre à la limite DWORD.
Si vous utilisez Microsoft Visual C++, une option du compilateur vous indique la taille réelle de votre objet:/d1reportSingleClassLayout
C'est non documenté sauf pour cette vidéo de Lavavej http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-3-of-n
Si vous souhaitez examiner la mise en page d'une structure particulière, la macro offsetof(s,member)
peut également être utile. Il vous indique à quelle distance de l'adresse de base d'une structure un membre particulier réside:
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;
}
Souhaitez-vous imprimer sur une machine 32 bits typique:
foo->a is 0 bytes into a foo
foo->b is 4 bytes into a foo
Considérant que sur une machine 64 bits typique, il serait imprimer
foo->a is 0 bytes into a foo
foo->b is 8 bytes into a foo
Ceci peut aider.
De plus, les fonctions de classe sont représentées comme toute autre fonction. La seule magie que C++ fait à la fonction est de modifier les noms de fonction pour identifier de manière unique une fonction spécifique avec un ensemble spécifique de paramètres dans une classe spécifique.
Il existe un appel à l'utilitaire pahole
(pour 'Poke-A-HOLE' ) qui est destiné à étudier le remplissage de la disposition des objets, mais il est très utile pour visualiser la taille et la disposition de l'objet en général.