Strukturauffüllung in C++

Lesezeit: 7 Minuten

Strukturauffullung in C
Baruch

Wenn ich eine habe struct Gibt es in C++ keine Möglichkeit, es sicher in eine Datei zu lesen/schreiben, die plattformübergreifend/Compiler-kompatibel ist?

Denn wenn ich das richtig verstehe, ‘polstert’ jeder Compiler je nach Zielplattform unterschiedlich.

  • Die Effizienz (Leistung), die durch die Ausführung binärer I/O gewonnen wird, rechtfertigt oft nicht die Ausgaben für Forschung, Design, Entwicklung und insbesondere Debugging und Wartung. Quellcode sollte einfach zu verstehen sein, aber nicht einfacher.

    – Thomas Matthäus

    22. März 2011 um 22:24 Uhr

Strukturauffullung in C
Nawaz

Nein. Das ist nicht möglich. Es ist wegen fehlende Standardisierung von C++ auf Binärebene.

Don-Box schreibt (zitiert aus seinem Buch Essential COM, Kapitel COM als besseres C++)

C++ und Portabilität

Ist die Entscheidung getroffen, eine C++-Klasse als DLL zu verteilen, steht man vor einer der die grundlegenden Schwächen von C++das ist, Mangel an Standardisierung auf der binären Ebene. Obwohl das ISO/ANSI C++ Draft Working Paper versucht zu kodifizieren, welche Programme kompiliert werden und welche semantischen Auswirkungen ihre Ausführung haben wird, es unternimmt keinen Versuch, das binäre Laufzeitmodell von C++ zu standardisieren. Dieses Problem tritt zum ersten Mal auf, wenn ein Client versucht, eine Verknüpfung mit der Importbibliothek der FastString-DLL aus einer C++-Entwicklungsumgebung herzustellen außer diejenige, die zum Erstellen der FastString-DLL verwendet wird.

Das Auffüllen von Strukturen wird von verschiedenen Compilern unterschiedlich durchgeführt. Selbst wenn Sie den gleichen Compiler verwenden, kann die Ausrichtung für das Packen von Strukturen unterschiedlich sein, je nachdem, was Pragma-Paket Sie verwenden.

Nicht nur das, wenn Sie zwei Strukturen schreiben, deren Mitglieder sind exakt gleich, die nur Unterschied ist, dass die Reihenfolge, in der sie deklariert werden, unterschiedlich ist, dann kann die Größe jeder Struktur unterschiedlich sein (und ist es oft).

Sehen Sie sich zum Beispiel dies an,

struct A
{
   char c;
   char d;
   int i;
};

struct B
{
   char c;
   int i;
   char d;
};

int main() {
        cout << sizeof(A) << endl;
        cout << sizeof(B) << endl;
}

Kompilieren Sie es mit gcc-4.3.4und Sie erhalten diese Ausgabe:

8
12

Das heißt, die Größen sind unterschiedlich, obwohl beide Strukturen die gleichen Mitglieder haben!

Das Fazit ist, dass der Standard nicht darüber spricht, wie das Auffüllen erfolgen soll, und daher können die Compiler jede Entscheidung treffen und Sie kann nicht Gehen Sie davon aus, dass alle Compiler die gleiche Entscheidung treffen.

  • Es gibt __attribute__((packed)) die ich sowohl für Shared-Memory-Strukturen als auch für die Abbildung von Netzwerkdaten verwende. Es wirkt sich auf die Leistung aus (vgl digitalvampire.org/blog/index.php/2006/07/31/… ), aber es ist eine nützliche Funktion für netzwerkbezogene Strukturen. (Soweit ich weiß, ist dies kein Standard, daher ist die Antwort immer noch wahr).

    – Pijusn

    8. Juni 2015 um 7:11 Uhr


  • Ich verstehe nicht, warum die Größe von Struct A 8 ist und nicht mehr. { Zeichen c; // was ist damit? Zeichen d; // Größe 1 + Polsterung von 3 int i; // Größe 4 };

    – Dchris

    3. März 2017 um 8:01 Uhr


  • @Dchris – der Compiler achtet wahrscheinlich darauf, dass jedes Feld basierend auf seiner eigenen natürlichen Ausrichtung ausgerichtet ist. c und d sind ein Byte und daher ausgerichtet, egal wo Sie sie für die Single-Byte-CPU-Anweisungen platzieren. Der int muss jedoch an einer 4-Byte-Grenze ausgerichtet werden, wofür zwei Bytes zum Auffüllen nach d erforderlich sind, um dorthin zu gelangen. Damit kommst du auf 8.

    – Hoodaticus

    25. Mai 2017 um 20:58 Uhr

  • Es scheint, als würden die meisten Compiler Mitglieder auf die gleiche Weise ausrichten. Gibt es wirklich Compiler da draußen, die Padding dazwischen setzen würden? A::c und A::d? Wenn dies nicht der Fall ist, liege ich richtig, wenn ich sage, dass das Problem nur darin besteht, dass der Standard keine Garantien gibt, obwohl jeder Compiler das Gleiche zu tun scheint (ähnlich wie ein reinterpret_cast).

    – Indiana Kernick

    10. Januar 2020 um 0:54 Uhr

Wenn Sie die Möglichkeit haben, die Struktur selbst zu entwerfen, sollte dies möglich sein. Die Grundidee ist, dass Sie es so entwerfen sollten, dass keine Füllbytes eingefügt werden müssen. Der zweite Trick besteht darin, dass Sie mit Unterschieden in der Endianität umgehen müssen.

Ich werde beschreiben, wie die Struktur mit Skalaren erstellt wird, aber Sie sollten in der Lage sein, verschachtelte Strukturen zu verwenden, solange Sie für jede enthaltene Struktur dasselbe Design anwenden.

Erstens ist eine grundlegende Tatsache in C und C++, dass die Ausrichtung eines Typs die Größe des Typs nicht überschreiten darf. Wenn dies der Fall wäre, wäre es nicht möglich, Speicher mit zuzuweisen malloc(N*sizeof(the_type)).

Gestalten Sie die Struktur, beginnend mit den größten Typen.

 struct
 {
   uint64_t alpha;
   uint32_t beta;
   uint32_t gamma;
   uint8_t  delta;

Füllen Sie als Nächstes die Struktur manuell auf, damit Sie am Ende den größten Typ finden:

   uint8_t  pad8[3];    // Match uint32_t
   uint32_t pad32;      // Even number of uint32_t
 }

Im nächsten Schritt muss entschieden werden, ob die Struktur im Little- oder Big-Endian-Format gespeichert werden soll. Der beste Weg ist, alle Elemente zu “tauschen”. vor Ort vor dem Schreiben oder nach dem Lesen der Struktur, wenn das Speicherformat nicht mit der Endianness des Hostsystems übereinstimmt.

  • Das klingt interessant. Aber können Sie mehr ins Detail gehen: Warum ordnen Sie es nach Typlänge absteigend und warum haben Sie es aufgefüllt, dass Sie eine gerade Anzahl von uint32_t haben?

    – Phil

    21. Februar 2015 um 22:52 Uhr

  • @Phil, Ein Grundtyp, wie uint32_t, kann (möglicherweise) eine Ausrichtungsanforderung haben, die seiner Größe entspricht, in diesem Fall vier Bytes. Ein Compiler kann Füllzeichen einfügen, um dies zu erreichen. Wenn Sie dies manuell tun, muss der Compiler dies nicht tun, da die Ausrichtung immer korrekt ist. Der Nachteil besteht darin, dass auf Systemen mit weniger strengen Ausrichtungsanforderungen eine manuell aufgefüllte Struktur größer ist als eine vom Compiler aufgefüllte. Sie können dies in aufsteigender oder absteigender Reihenfolge tun, aber Sie müssen mehr Pads in der Mitte der Struktur einfügen, wenn Sie int in aufsteigender Reihenfolge tun …

    – Lindydancer

    22. Februar 2015 um 8:34 Uhr

  • … Das Auffüllen am Ende der Struktur ist nur erforderlich, wenn Sie es in Arrays verwenden möchten.

    – Lindydancer

    22. Februar 2015 um 8:35 Uhr

  • @jwg. Im allgemeinen Fall (z. B. wenn Sie eine von jemand anderem entworfene Struktur verwenden) kann eine Auffüllung eingefügt werden, um sicherzustellen, dass kein Feld an einer Stelle landet, die die Hardware nicht lesen kann (wie in den anderen Antworten erläutert). Wenn Sie die Struktur jedoch selbst entwerfen, können Sie mit etwas Sorgfalt sicherstellen, dass keine Polsterung erforderlich ist. Diese beiden Tatsachen stehen einander in keiner Weise entgegen! Ich glaube, dass diese Heuristik für alle möglichen Architekturen gilt (da ein Typ keine Ausrichtungsanforderung hat, die größer als seine Größe ist, was in C sowieso nicht zulässig ist).

    – Lindydancer

    13. August 2015 um 9:48 Uhr

  • @Lindydancer – Padding ist erforderlich, wenn Sie beabsichtigen, sie zu einem zusammenhängenden Speicherblock aus zufälligem Material zusammenzusetzen, nicht unbedingt nur zu einem homogenen Array. Durch das Auffüllen können Sie sich an beliebigen Grenzen wie sizeof(void*) oder der Größe eines SIMD-Registers selbst ausrichten.

    – Hoodaticus

    25. Mai 2017 um 21:02 Uhr


Nein, es gibt keinen sicheren Weg. Zusätzlich zum Auffüllen müssen Sie sich mit unterschiedlicher Byte-Reihenfolge und unterschiedlichen Größen von integrierten Typen befassen.

Sie müssen ein Dateiformat definieren und Ihre Struktur in und aus diesem Format konvertieren. Serialisierungsbibliotheken (z. B. boost::serialization oder die Protokollpuffer von Google) können dabei helfen.

  • “Die Größe einer Struktur (oder Klasse) ist möglicherweise nicht gleich der Summe der Größe ihrer Mitglieder.”

    – Thomas Matthäus

    22. März 2011 um 22:21 Uhr

  • @Thomas: Genau. Und das ist erst der Anfang des Spaßes.

    – Erich

    22. März 2011 um 22:23 Uhr

Lange Rede kurzer Sinn, nein. Es gibt keinen plattformunabhängigen, standardkonformen Umgang mit Padding.

Padding wird im Standard “Alignment” genannt und beginnt in 3.9/5 mit der Diskussion:

Objekttypen haben Ausrichtungsanforderungen (3.9.1, 3.9.2). Die Ausrichtung eines vollständigen Objekttyps ist ein implementierungsdefinierter ganzzahliger Wert, der eine Anzahl von Bytes darstellt; ein Objekt wird einer Adresse zugewiesen, die die Ausrichtungsanforderungen seines Objekttyps erfüllt.

Aber von dort aus geht es weiter und windet sich in viele dunkle Ecken des Standards. Die Ausrichtung ist “implementierungsdefiniert”, was bedeutet, dass sie zwischen verschiedenen Compilern oder sogar zwischen Adressmodellen (dh 32-Bit/64-Bit) unter dem unterschiedlich sein kann gleich Compiler.

Wenn Sie keine wirklich strengen Leistungsanforderungen haben, sollten Sie Ihre Daten in einem anderen Format auf Disc speichern, z. B. Zeichenketten. Viele Hochleistungsprotokolle senden alles mit Strings, obwohl das natürliche Format etwas anderes sein könnte. Zum Beispiel sendet ein Austausch-Feed mit niedriger Latenz, an dem ich kürzlich gearbeitet habe, Datumsangaben als Zeichenfolgen im folgenden Format: “20110321” und Zeiten werden ähnlich gesendet: “141055.200”. Obwohl dieser Austausch-Feed den ganzen Tag über 5 Millionen Nachrichten pro Sekunde sendet, verwenden sie immer noch Strings für alles, weil sie auf diese Weise Endianness und andere Probleme vermeiden können.

992790cookie-checkStrukturauffüllung in C++

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy