sizeof, unerklärlich
-
Eine meiner Klassen braucht ein paar Bytes zu viel, und da habe ich folgenden Testcode geschrieben:
class a { bool c; }; class b { int d; }; class c { bool c; int d; }; cout << "Size of a: "<<sizeof(a)<<endl; cout << "Size of b: "<<sizeof(b)<<endl; cout << "Size of c: "<<sizeof(c)<<endl;Zu meinem Erstaunen ist die Ausgabe:
Size of a: 1 Size of b: 4 Size of c: 8Warum um alles in der Welt braucht die Klasse c 8 Bytes? Das ganze auf einem 64bit-Linux-Rechner mit g++ compiliert.
lg
-
Der Compiler "optimiert" die Memory Struktur deiner Klasse. Das bedeutet, er füllt z.B. mit 3 Byte nach, damits gerade 8 Bytes ergibt. Das nennt man Padding.
Bei den meisten Kompilern kann das Padding ausgeschaltet werden.
Simon
-
Das nennt sich Alignment. 1 Byte für den Boolean, 4 Byte für den Integer und + 3 Byte um es auf eine durch 4 teilbare Zahl zu machen. Unter Visual Studio kann man diese Optimierung mit Direktiven ausschalten. Das sollteste aber nur machen, wenn du irgendwas aus einer Datei ausliest und dafür die korrekte Anzahl der Bytes brauchst.
EDIT:
Hupsi, der Hasenpups über mir war schneller
-
Da es bisher noch keiner gesagt hat: Das Einfügen von Padding hat natürlich einen guten Grund. Der Zugriff auf ein 32-Bit-Wort ist langsamer, wenn es nicht an einer durch 4 teilbaren Adresse liegt.
-
Bashar schrieb:
Da es bisher noch keiner gesagt hat: Das Einfügen von Padding hat natürlich einen guten Grund. Der Zugriff auf ein 32-Bit-Wort ist langsamer, wenn es nicht an einer durch 4 teilbaren Adresse liegt.
Vorausgesetzt er ist überhaupt zulässig. Generell für es zu nicht standardkonformem Code, denn ein nicht ausgerichtetes int ist kein int.
Sauber ist in solchen Serialisierungsfällen die Verwendung eines char-Arrays, in das die Werte einzeln kopiert (z.b. mit memcpy) bzw. zurückkopiert werden. Das wird im allgemeinen auch zu besserer Strukturierung führen, denn diese Art von Operation sollte ohnehin nicht im gesamten Programm verteilt durchgeführt werden. Ganz nebenbei kann man dann auch noch Behandlung für besondere Formate, Endianess u.ä. einbauen, ohne dass das den Rest des Codes beeinflusst.
-
camper schrieb:
Bashar schrieb:
Da es bisher noch keiner gesagt hat: Das Einfügen von Padding hat natürlich einen guten Grund. Der Zugriff auf ein 32-Bit-Wort ist langsamer, wenn es nicht an einer durch 4 teilbaren Adresse liegt.
Vorausgesetzt er ist überhaupt zulässig. Generell für es zu nicht standardkonformem Code, denn ein nicht ausgerichtetes int ist kein int.
Mir ging es um die technischen Ursachen. Ein 32-Bit-Wort kann, wenn die Architektur es nicht anders unterstützt, notfalls manuell Byte für Byte herangeschaufelt werden, und das ist freilich langsamer als ein direkter Zugriff.
Der Standard kommt erst jetzt ins Spiel: Es ist nicht gefordert, dass ein Compiler das auch so umsetzt, d.h. er darf Alignment-Forderungen stellen und sich zum einen weigern, Code zu erzeugen, der trotz falschem Alignment richtig arbeitet, und zum anderen das Objektlayout so festlegen, dass alle Typen ausgerichtet sind.
-
Bashar schrieb:
Da es bisher noch keiner gesagt hat: Das Einfügen von Padding hat natürlich einen guten Grund. Der Zugriff auf ein 32-Bit-Wort ist langsamer, wenn es nicht an einer durch 4 teilbaren Adresse liegt.
Das ist nicht nur langsamer, sondern kann sogar falsch sein. Auf einem Sparc-Prozessor sind solche misaligned Zugriffe nicht erlaubt und führen zu einer Exception. Und nicht im C++-Sinn sondern eine Prozessor-exception.
Zur Optimierung empfiehlt es sich, die Datenelemente zu sortieren, so dass byte-Typen wie bool oder char am Ende angeordnet sind. Eine Struktur aus "bool, int, bool, int" benötigt 2 mal padding, aber eine Struktur "int, int, bool, bool" nur ein mal. Und das noch dazu weniger:
#include <iostream> struct A { bool a; int b; bool c; int d; }; struct B { int b; int d; bool a; bool c; }; int main(int argc, char* argv[]) { std::cout << sizeof(A) << '\t' << sizeof(B) << std::endl; }gibt 16 und 12 aus (auf 32 bit Linux/gcc). Im ersten Fall haben wir also insgesamt 6 padding Bytes und im 2. Fall nur 2, obwohl beide Strukturen in der Regel funktional identisch sind.
-
tntnet schrieb:
Bashar schrieb:
Da es bisher noch keiner gesagt hat: Das Einfügen von Padding hat natürlich einen guten Grund. Der Zugriff auf ein 32-Bit-Wort ist langsamer, wenn es nicht an einer durch 4 teilbaren Adresse liegt.
Das ist nicht nur langsamer, sondern kann sogar falsch sein. Auf einem Sparc-Prozessor sind solche misaligned Zugriffe nicht erlaubt und führen zu einer Exception. Und nicht im C++-Sinn sondern eine Prozessor-exception.
Nein, siehe mein letztes Posting.
-
Aufregend, wieder was gelernt, danke. Vor allem, daß die Reihenfolge innerhalb
der Deklaration entscheidend für den Speicherbedarf ist. Nun noch ein Beispiel:class a { int a; Point* b; int c; }; class b { int a; int c; Point* b; };Klasse b braucht 16 Bytes. Für Klasse a hätte ich erwartet, daß das
Padding nur 4 Bytes ausmacht. Aber Klasse a braucht 24 Bytes. Hm?
-
Schau doch, wie gross bei dir
Point*undintsind. Wahrscheinlich 8 (64-Bit-Zeiger) und 4 Byte. Die Klasseawird wohl so ausgerichtet:[b]Bytes[/b] [b]Membertyp[/b] [0,8[ int [8,16[ Point* [16,24[ intP.S. Wird das wirklich kompiliert, wenn du Nicht-Konstruktor-Member gleich wie die Klasse nennst?
-
Nexus schrieb:
P.S. Wird das wirklich kompiliert, wenn du Nicht-Konstruktor-Member gleich wie die Klasse nennst?
Ich habs mal getestet und
#include <iostream> using namespace std; class a { int a; int* b; int c; }; class b { int a; int c; int* b; }; int main() { cout<<sizeof(a)<<'\n'; cout<<sizeof(b)<<'\n'; }compiliert.
Aber um die Gefahr zu unmgehen, kann man ja die Namen der Klasse weglassen und nur zwei Objekte machen.
#include <iostream> using namespace std; class { int a; int* b; int c; } a; class { int a; int c; int* b; } b; int main() { cout<<sizeof(a)<<'\n'; cout<<sizeof(b)<<'\n'; }