C++ vector of struct sizemäßig "normalisieren" für 32+64bit

T_55

Lieutenant
Registriert
Feb. 2013
Beiträge
643
Hallo,

mir ist aufgefallen, dass eine einheitliche Erschaffung von Daten, für 32bit sowie 64bit Systeme, gar nicht so einfach ist wie gedacht. Natürlich hab ich ganz brav für eine "sichere" einheitliche Größe der Daten sowas wie std::int64_t genutzt, bei doubles und bool passt es wohl naturgemäß.
ABER nun das interessante (zumindest für mich): Packe ich verschiedene Daten (welche selber bei 32+64bit die exakt gleiche sizeof() besitzen) in ein Struct:
2x bool
5x double
2x std::int64_t
und erstelle einen Vector mit Datentyp des Structs dann:

32bit:
  • sizeof(struct) = 60 byte
  • sizeof(vector(struct)) = 12 byte
64bit:
  • sizeof(struct) = 64 byte
  • sizeof(vector(struct)) = 24 byte

Das sich trotz gleichem Inhalt sogar das Struct von der Größe unterschiedet hat mich tatsächlich überrascht.
Jetzt wäre die Frage was wäre die Alternative zu einem vector mit struct um bei 32 und 64 bit die gleichen Größen zu erreichen?

Grüße
Ergänzung ()

ps: ok liegt wahrscheinlich am padding aber wie bekommt man das "sicher" auf einen Nenner?
 
Zuletzt bearbeitet:
Das könnte am alignment der Datenstruktur liegen. Häufig legt der Compiler die einzelnen Elemtente einer Struktur auf die nächste 32 oder 64 Bit-Adresse um Zugriffe zu beschleunigen (die Größe der Struktur erhöht sich dann dementsprechend). Falls du den GCC verwendest, versuche mal folgendes:

https://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Structure_002dPacking-Pragmas.html
Ergänzung ()

T_55 schrieb:
ps: ok liegt wahrscheinlich am padding aber wie bekommt man das "sicher" auf einen Nenner?
Indem du z.B. das padding auf 1 stellst, also keine "Lücken" in der Struktur zulässt
 
  • Gefällt mir
Reaktionen: T_55
Der Inhalt Von einem vektor hat doch nichts sizeof zu tun:
Die objekte liegen auf dem heap.
Zwischen 64 bit und 32 bit unterscheiden sich z.b. die zeiger, welche auf den heap (zu deinen structs) zeigen.
Und vector hat bestimmt noch weitere private member, z.b. die anzahl Der objekte im vektor, welche sich zwischen 64 und 32 bit auch in Der größe unterscheiden könnten.

Verbessert mich, wenn ich jetzt total daneben liege...
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: T_55 und BlackMark
Danke euch das hat geklappt! structs sind jetzt identisch mit 58 byte
#pragma pack(push, 1)
// struct
#pragma pack(pop)

ps: die sizeof vom vector ist zwar noch unterschiedlich aber das ist wohl so...
 
Hallo,

Also entweder läuft dein Programm auf 64 oder 32 bit, beides gleichzeitig geht nicht.

Wenn du nun DATEN zwischen 32 und 64 Bit Installationen deines Programms austauschbar machen musst, dann schreib für die relevanten Klassen bitte einen Serialisierer wo du genau kontrollieren kannst wie das in einer Datei landen soll, alles andere ist Compiler-Versions-Lotterie.

Grüße,
Znep
 
Wenn du die beiden Bools nach unten packst, sollte es in dem Fall auch ohne Pragma laufen. Und auch mit Pragma dürfte das der Performance zuträglich sein, sofern benötigt.
 
T_55 schrieb:
ps: die sizeof vom vector ist zwar noch unterschiedlich aber das ist wohl so...
Von C++ habe ich jetzt nich so viel Ahnung, komme mehr aus der C-Ecke, aber:

sizeof(vector(struct)) kann wahrscheinlich 3 Adressen fassen.
Auf 32-Bit ist eine Adresse 32-Bit groß/lang/breit, entsprechend 4 Byte. Auf 64-Bit -> 64-Bit = 8 Byte. Das ganze jeweils mal 3 und man landet bei 12 bzw. 24 Byte.
 
znep schrieb:
alles andere ist Compiler-Versions-Lotterie.
Soll heißen pragma pack funktioniert nicht verlässlich?
Ergänzung ()

NeoExacun schrieb:
Wenn du die beiden Bools nach unten packst, sollte es in dem Fall auch ohne Pragma laufen.
Hatte ich sogar probiert gab trotzdem ein unterschied in der Größe. Aber in absteigender Membergröße ist immer gut um den Platz am besten auszunutzen.
 
znep schrieb:
Wenn du nun DATEN zwischen 32 und 64 Bit Installationen deines Programms austauschbar machen musst, dann schreib für die relevanten Klassen bitte einen Serialisierer wo du genau kontrollieren kannst wie das in einer Datei landen soll, alles andere ist Compiler-Versions-Lotterie.
Serialisierer schreiben kann man sich sparen - protobuf ...
Aber wenns um Performance geht (z.B. geteilter Speicher zwischen den Prozessen), dann geht wenig über Binärkompatibilität.
 
T_55 schrieb:
Soll heißen pragma pack funktioniert nicht verlässlich?
Pragma pack funktioniert auf jeden Fall verlässlich. Es kann nur sein, dass dein Compiler kein pragma pack unterstützt, sollte bei aktuellen Compilern aber kein Problem sein. gcc, clang und MSVC++ können alle pragma pack.

Der Vorschlag von @new Account() ist gut, du musst dir nicht deinen eigenen Serializer schreiben, sondern kannst protobuf verwenden. Alternativ kannst du auch boost::serialization verwenden.

Wenn du Datenaustausch zwischen Prozessen auf verschiedenen Systemen oder zwischen 32 und 64 bit Prozessen auf dem gleichen System hast, solltest du aber auf jeden Fall einen Serializer verwenden.

Gruß
BlackMark
 
  • Gefällt mir
Reaktionen: T_55
T_55 schrieb:
Soll heißen pragma pack funktioniert nicht verlässlich?
Ergänzung ()

Das funktioniert zuverlässig solange du immer den gleichen Compiler verwendest und alles genau gleich kompilierst. Wo ich Unterschiede gesehen habe ist z.B. zwischen Debug und Release build. Wenn du C++ STL verwendest sei dir bewusst das die Teil der C++ Runtime Libraries sind, und hier das Speicherlayout sich von Version zu Version ändern kann.

Falls du nur ein kleines Programm für dich selber schreibst ist das alles irrelevant. Bei einer Applikation die in einer Firma vielleicht 10-20 Jahre im Einsatz ist sind das die Themen wo ich die Fehler anderer Leute suchen und ausbessern darf...

Grüße,
Znep
 
znep schrieb:
Wo ich Unterschiede gesehen habe ist z.B. zwischen Debug und Release build. Wenn du C++ STL verwendest sei dir bewusst das die Teil der C++ Runtime Libraries sind, und hier das Speicherlayout sich von Version zu Version ändern kann.

Falls du nur ein kleines Programm für dich selber schreibst ist das alles irrelevant. Bei einer Applikation die in einer Firma vielleicht 10-20 Jahre im Einsatz ist sind das die Themen wo ich die Fehler anderer Leute suchen und ausbessern darf...

Grüße,
Znep

Kann man ganz einfach lösen:

In der Datenschicht der Serialisierung einfach keinerlei STL Klassen ablegen, dafür nur reine structs oder Binärdaten. Fertig. Beim Deserialisieren kann man dann die STL Container wieder befüllen - aber direkt serialisieren würde ich diese niemals, da ein ein sizeof(std::vector<myStruct>) je nach Compiler/Platform unterschiedliche Werte liefert - wie du ja bereits schon angemerkt hast.

Allerdings hatte ich noch nie einen Fall, das unterschiedliche Buildkonfigurationen (Debug vs Release, Optimierung vs nicht Optimierung, Debugsymbole an/aus), unterschiedliche sizeof() bei POD-Structs liefern. (POD = Plain-Old-Data).
 
Es gibt ein paar Dinge, die hier zu beachten sind:
1. sizeof(bool) ist implementation-defined und ein guter Kandidat, sich auf Architekturen unterschiedlicher Breite zu ändern.
2. sizeof(double) ist auch nicht standardisiert. In praktisch jeder Implementierung wird das allerdings ein IEEE 754 double sein und damit 8 Bytes.
3. Alignment. Ein 8 Byte member kann nicht überall im Speicher stehen. Deshalb kann man structs manuell packen, indem man die member nach der Größe ordnet. Z.B.:
Code:
struct { char c; int i; } s;
(char*) &s.i != &s.c + 1 /* Ausnahme: sizeof(int) == sizeof(char) */

Das ist der Fall, weil die meisten Architekturen typischerweise nicht Byte-weise adressieren können, sondern nur Wort-weise, also müssen die Struct-Members word-aligned sein. Was nun die Wortbreite ist, hängt wieder von der Architektur ab. D.h. nach dem Char-Member im obigen Struct werden typischerweise 3 oder 7 Byte padding stehen.
 
Zurück
Oben