Angenommen, ich habe eine Klasse Temp:
class Temp
{
public:
int function1(int foo) { return 1; }
void function2(int bar) { foobar = bar; }
private:
int foobar;
};
Wenn ich ein Objekt der Klasse Temp erstelle, wie würde ich berechnen, wie viel Speicherplatz benötigt wird und wie es im Speicher dargestellt wird (z. B. | 4 Byte für foobar | 8 Byte für Funktion1 | etc |)
In erster Näherung ist die Größe eines Objekts die Summe der Größen seiner konstituierenden Datenelemente. Sie können sicher sein, dass es niemals kleiner sein wird.
Genauer gesagt, der Compiler ist berechtigt, einen Abstand zwischen Datenelementen einzufügen, um sicherzustellen, dass jedes Datenelement die Ausrichtungsanforderungen der Plattform erfüllt. Einige Plattformen sind hinsichtlich der Ausrichtung sehr streng, während andere (x86) eher fehlerverzeihend sind, sich jedoch bei korrekter Ausrichtung deutlich verbessern. Daher kann sogar die Compiler-Optimierungseinstellung die Objektgröße beeinflussen.
Vererbung und virtuelle Funktionen sind eine zusätzliche Komplikation. Wie andere bereits gesagt haben, beanspruchen die Mitgliedsfunktionen Ihrer Klasse selbst keinen "pro Objekt" -Raum, aber das Vorhandensein von virtuellen Funktionen in der Schnittstelle dieser Klasse impliziert im Allgemeinen das Vorhandensein einer virtuellen Tabelle, im Wesentlichen einer Nachschlagetabelle der verwendeten Funktionszeiger Die dynamische Funktionsimplementierung dynamisch auflösen, die zur Laufzeit aufgerufen werden soll. Auf die virtuelle Tabelle (vtbl) wird im Allgemeinen über einen in jedem Objekt gespeicherten Zeiger zugegriffen.
Abgeleitete Klassenobjekte umfassen auch alle Datenmitglieder ihrer Basisklassen.
Schließlich geben Zugriffsbezeichner (öffentlich, privat, geschützt) dem Compiler einen gewissen Spielraum beim Packen von Datenmitgliedern.
Die kurze Antwort lautet, dass sizeof (myObj) oder sizeof (MyClass) immer die richtige Größe eines Objekts angibt, das Ergebnis ist jedoch nicht immer leicht vorhersagbar.
sizeof(Temp)
wird dir die Größe geben. Am wahrscheinlichsten sind es 4 Bytes (bei einer Vielzahl von Annahmen), und das ist nur für das int. Die Funktionen nehmen pro Objekt keinen Platz ein, sie werden einmal kompiliert und bei jeder Verwendung vom Compiler verknüpft.
Es ist unmöglich genau zu sagen, wie das Objektlayout ist, der Standard definiert jedoch nicht die binäre Darstellung von Objekten.
Es gibt ein paar Dinge, die Sie bei binären Repräsentationen beachten sollten, da sie nicht unbedingt die Summe der Bytes der Datenelemente sind, z. B. durch Strukturauffüllung .
Ich habe mich schon immer über so etwas gewundert und mich entschlossen, eine vollständige Antwort zu finden. Es geht darum, was Sie vielleicht erwarten, und es ist vorhersehbar (yay)! Mit den folgenden Informationen sollten Sie also in der Lage sein, die Größe einer Klasse vorherzusagen.
Ich habe festgestellt, dass bei Verwendung von Visual Studio Community 2017 (Version 15.2) im Release-Modus alle Optimierungen deaktiviert und RTTI ( Informationen zum Laufzeit-Typ ) deaktiviert sind folgende:
Kurzantwort:
Als erstes:
<size of pointer> == 4
Bytes<size of pointer> == 8
Bytesclass ChildClass: virtual public ParentClass
Meine Erkenntnisse sind nun:
<size of variable>
Bytes<size of variables>
Bytes<size of pointer>
Bytes<size of pointer>
Bytes wird<size of pointer>
<size of pointer>
Bytes zur Gesamtsumme hinzu , Umschließen aller Mitgliedsvariablen in so viele <size of pointer>
- Byte-Inkremente, wie zur Abdeckung von <total size of member variables>
erforderlich sind - ja, Sie haben das richtig gelesen ... (siehe meine Vermutung, was in vor sich geht. Schlussfolgerungen ...)Beachten Sie , dass ich sogar versucht habe, mit function () einen Text zu schreiben, eine Instanz der Klasse zu erstellen und die Funktion aufzurufen. es ändert nicht die Größe der Funktionsklasse (es ist keine Optimierung)! Ich war etwas überrascht, aber es macht tatsächlich Sinn: Mitgliedsfunktionen ändern sich nicht, sodass sie außerhalb der Klasse selbst gespeichert werden können.
Schlussfolgerungen:
<size of pointer>
Bytes und fügt dieser Klasse <size of pointer>
Bytes hinzu. Diese vtable kann natürlich nur einmal pro Klasse existieren (entweder oder nicht).<size of pointer>
Bytes erhöht, auch wenn dies der Fall ist muss nicht so viel Speicher verbrauchen, da es einen vtable "helper block" für jedes <size of pointer>
Byte Speicher oder so hinzufügt ...Lange Antwort:
Ich habe dies alles mit diesem Code bestimmt:
#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");
}
Ausgabe:
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 (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)
Wenn Sie Informationen zur Mehrfachvererbung wünschen, finden Sie es selbst heraus! -.-
Wenn Sie detaillierte Informationen darüber wünschen, wie Objekte zur Laufzeit im Speicher dargestellt werden, ist die ABI-Spezifikation ( Application Binary Interface ) die richtige Stelle. Sie müssen feststellen, welche ABI Ihr Compiler implementiert. GCC-Versionen 3.2 und höher implementieren beispielsweise die Itanium C++ ABI .
Methoden gehören zur Klasse, nicht zu einem bestimmten instanziierten Objekt.
Wenn keine virtuellen Methoden vorhanden sind, entspricht die Größe eines Objekts der Summe der Größe seiner nicht statischen Elemente plus dem optionalen Auffüllen zwischen den Elementen für die Ausrichtung. Die Member werden wahrscheinlich sequentiell im Speicher abgelegt, aber die Spezifikation garantiert nicht die Reihenfolge zwischen Abschnitten mit unterschiedlichen Zugriffsspezifikationen oder die Anordnung in Bezug auf das Layout der Oberklassen.
Bei vorhandenen virtuellen Methoden kann zusätzlicher Platz für vtable und andere RTTI-Informationen benötigt werden.
Auf den meisten Plattformen wird ausführbarer Code in den schreibgeschützten .text
(oder ähnlich benannten) Abschnitt der ausführbaren Datei oder Bibliothek geschrieben und niemals irgendwo kopiert. Wenn class Temp
über die Methode public: int function1(int)
verfügt, können die Temp
-Metadaten einen Zeiger auf eine _ZN4Temp9function1Ei
-Funktion (entstellter Name können je nach Compiler unterschiedlich sein) für die eigentliche Implementierung aufweisen, sie enthalten jedoch niemals den eingebetteten ausführbaren Code.
Elementfunktionen berücksichtigen nicht die Größe der Objekte einer bestimmten Klasse. Die Größe des Objekts hängt nur von den Mitgliedsvariablen ab. Bei Klassen, die virtuelle Funktionen enthalten, wird die VPTR zum Objektlayout hinzugefügt. Die Größe der Objekte ist also im Wesentlichen die Größe der Elementvariablen + die Größe der VPTRs. Dies kann manchmal nicht zutreffen, da Compiler versuchen, Mitgliedsvariablen an der DWORD-Grenze zu finden.
Wenn Sie Microsoft Visual C++ verwenden, gibt es eine Compileroption, die Sie darüber informiert, wie groß Ihr Objekt tatsächlich ist:/d1reportSingleClassLayout
Es ist undokumentiert, außer für dieses Video von Lavavej http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Advanced-STL-3-of-n
Wenn Sie das Layout einer bestimmten Struktur untersuchen möchten, kann auch das Makro offsetof(s,member)
von Nutzen sein. Hier erfahren Sie, wie weit von der Basisadresse einer Struktur ein bestimmtes Mitglied entfernt ist:
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;
}
Würde auf einem typischen 32-Bit-Computer drucken:
foo->a is 0 bytes into a foo
foo->b is 4 bytes into a foo
Auf einer typischen 64-Bit-Maschine würde es drucken
foo->a is 0 bytes into a foo
foo->b is 8 bytes into a foo
Das kann helfen.
Darüber hinaus werden Klassenfunktionen wie jede andere Funktion dargestellt. Die einzige Magie, die C++ für die Funktion ausübt, besteht darin, die Funktionsnamen zu manipulieren, um eine bestimmte Funktion eindeutig mit einem bestimmten Parametersatz innerhalb einer bestimmten Klasse zu identifizieren.
Es gibt einen Utility-Aufruf pahole
(für 'Poke-A-HOLE' ), der nominell untersuchen soll, wie Objektlayouts aufgefüllt werden, sich aber hervorragend für die Visualisierung von Objektgröße und -layout im Allgemeinen eignet.