867 Stimmen

Warum ist sizeof für eine Struktur nicht gleich der Summe von sizeof der einzelnen Mitglieder?

Warum ist die sizeof Operator für eine Struktur eine Größe zurück, die größer ist als die Gesamtgröße der Mitglieder der Struktur?

20 Stimmen

Siehe diese C-FAQ zur Speicherverwaltung. c-faq.com/struct/align.esr.html

71 Stimmen

Eine Anekdote: Es gab tatsächlich einen Computervirus, der seinen Code in Struktur-Paddings im Wirtsprogramm unterbrachte.

10 Stimmen

@Elazar Das ist beeindruckend! Ich hätte es nie für möglich gehalten, so winzige Flächen für irgendetwas zu nutzen. Können Sie weitere Einzelheiten nennen?

11voto

sid1138 Punkte 176

Die Größe einer Struktur ist größer als die Summe ihrer Teile, was auf die so genannte Packung zurückzuführen ist. Ein bestimmter Prozessor hat eine bevorzugte Datengröße, mit der er arbeitet. Die bevorzugte Größe der meisten modernen Prozessoren ist 32 Bit (4 Byte). Der Zugriff auf den Speicher ist effizienter, wenn die Daten innerhalb dieser Grenze liegen, als wenn sie sich über diese Grenze hinaus erstrecken.

Zum Beispiel. Betrachten Sie die einfache Struktur:

struct myStruct
{
   int a;
   char b;
   int c;
} data;

Wenn es sich um einen 32-Bit-Rechner handelt und die Daten an einer 32-Bit-Grenze ausgerichtet sind, ergibt sich sofort ein Problem (vorausgesetzt, es gibt keine Strukturausrichtung). In diesem Beispiel nehmen wir an, dass die Strukturdaten an der Adresse 1024 beginnen (0x400 - beachten Sie, dass die untersten 2 Bits Null sind, so dass die Daten an einer 32-Bit-Grenze ausgerichtet sind). Der Zugriff auf data.a wird problemlos funktionieren, da er an einer Grenze - 0x400 - beginnt. Der Zugriff auf data.b funktioniert ebenfalls problemlos, da er an der Adresse 0x404 erfolgt - ebenfalls eine 32-Bit-Grenze. Aber eine nicht ausgerichtete Struktur würde data.c an die Adresse 0x405 setzen. Die 4 Bytes von data.c liegen an 0x405, 0x406, 0x407, 0x408. Auf einem 32-Bit-Rechner würde das System data.c in einem Speicherzyklus lesen, aber nur 3 der 4 Bytes erhalten (das 4. Byte liegt an der nächsten Grenze). Das System müsste also einen zweiten Speicherzugriff durchführen, um das 4. Byte zu erhalten,

Wenn nun der Compiler, anstatt data.c an Adresse 0x405 zu platzieren, die Struktur um 3 Bytes auffüllt und data.c an Adresse 0x408 platziert, dann würde das System nur 1 Zyklus benötigen, um die Daten zu lesen, was die Zugriffszeit auf dieses Datenelement um 50 % verkürzt. Das Auffüllen tauscht Speichereffizienz gegen Verarbeitungseffizienz. In Anbetracht der Tatsache, dass Computer über riesige Speichermengen verfügen können (viele Gigabyte), halten die Compiler den Tausch (Geschwindigkeit gegen Größe) für sinnvoll.

Leider wird dieses Problem zum Killer, wenn Sie versuchen, Strukturen über ein Netzwerk zu senden oder sogar die Binärdaten in eine Binärdatei zu schreiben. Das zwischen den Elementen einer Struktur oder Klasse eingefügte Padding kann die an die Datei oder das Netzwerk gesendeten Daten stören. Um portablen Code zu schreiben (der von mehreren verschiedenen Compilern verwendet werden kann), müssen Sie wahrscheinlich auf jedes Element der Struktur einzeln zugreifen, um die richtige "Verpackung" zu gewährleisten.

Andererseits haben verschiedene Compiler unterschiedliche Fähigkeiten, das Packen von Datenstrukturen zu verwalten. In Visual C/C++ zum Beispiel unterstützt der Compiler den Befehl #pragma pack. Damit können Sie die Packung und Ausrichtung von Daten anpassen.

Zum Beispiel:

#pragma pack 1
struct MyStruct
{
    int a;
    char b;
    int c;
    short d;
} myData;

I = sizeof(myData);

Ich sollte jetzt die Länge 11 haben. Ohne das Pragma könnte ich alles von 11 bis 14 (und bei manchen Systemen sogar 32) sein, je nach der Standardpackung des Compilers.

9voto

C99 N1256 Standardentwurf

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

6.5.3.4 Der sizeof-Operator :

3 Bei Anwendung auf einen Operanden vom Typ Struktur oder Vereinigung, ist das Ergebnis die Gesamtzahl der Bytes in einem solchen Objekt, einschließlich interner und nachfolgender Auffüllungen.

6.7.2.1 Struktur- und Vereinigungsbezeichner :

13 ... Es kann unbenannte Auffüllungen innerhalb eines Strukturobjekts geben, aber nicht an dessen Anfang.

und:

15 Am Ende einer Struktur oder Vereinigung kann es unbenannte Auffüllungen geben.

Die neue C99 flexibles Array-Member-Feature ( struct S {int is[];}; ) kann sich auch auf die Polsterung auswirken:

16 Als Sonderfall kann das letzte Element einer Struktur mit mehr als einem benannten Glied einen unvollständigen Array-Typ haben; dies wird als flexibles Array-Mitglied bezeichnet. In den meisten Situationen, wird das flexible Array-Mitglied ignoriert. Insbesondere ist die Größe der Struktur so, als ob das flexible Array-Mitglied weggelassen würde, außer dass es mehr Füllung am Ende haben kann als das Weglassen implizieren würde.

Anhang J Fragen der Übertragbarkeit bekräftigt:

Die folgenden sind nicht spezifiziert: ...

  • Der Wert der Füllbytes bei der Speicherung von Werten in Strukturen oder Unions (6.2.6.1)

C++11 N3337 Standardentwurf

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

5.3.3 Größe der :

2 Bei Anwendung auf eine Klasse angewandt, ist das Ergebnis die Anzahl der Bytes in einem Objekt dieser Klasse, einschließlich aller Auffüllungen, die für die Objekte dieses Typs in einem Array zu platzieren.

9.2 Mitglieder der Klasse :

Ein Zeiger auf ein Standard-Layout-Struct-Objekt, das mit einem reinterpret_cast entsprechend konvertiert wurde, zeigt auf sein Anfangsglied (oder, wenn dieses Mitglied ein Bitfeld ist, auf die Einheit, in der es sich befindet) und umgekehrt. [Anmerkung: Innerhalb eines struct-Objekts mit Standard-Layout kann es daher unbenanntes Padding geben, aber nicht an seinem Anfang, wie es für eine angemessene Ausrichtung erforderlich ist. - end note ]

Ich kenne nur genug C++, um die Notiz zu verstehen :-)

8voto

Orion Adrian Punkte 18345

Dies ist möglich, wenn Sie die Ausrichtung der Struktur implizit oder explizit festgelegt haben. Eine Struktur, die auf 4 ausgerichtet ist, wird immer ein Vielfaches von 4 Bytes sein, selbst wenn die Größe ihrer Mitglieder nicht ein Vielfaches von 4 Bytes ist.

Auch kann eine Bibliothek unter x86 mit 32-Bit-Ints kompiliert sein, und der Vergleich ihrer Komponenten auf einem 64-Bit-Prozess würde zu einem anderen Ergebnis führen, wenn Sie dies von Hand tun würden.

7voto

Die Sprache C lässt dem Compiler eine gewisse Freiheit, was die Position der Strukturelemente im Speicher angeht:

  • Speicherlöcher können zwischen zwei beliebigen Komponenten und nach der letzten Komponente auftreten. Dies war darauf zurückzuführen, dass bestimmte Arten von Objekten auf dem Zielcomputer durch die Grenzen der Adressierung begrenzt sein können
  • Größe der "Speicherlöcher", die im Ergebnis des sizeof-Operators enthalten ist. Der sizeof-Operator schließt nur die Größe des flexiblen Arrays nicht ein, das in C/C++ verfügbar ist
  • Einige Implementierungen der Sprache erlauben es Ihnen, das Speicherlayout von Strukturen durch Pragma- und Compiler-Optionen zu steuern

Die Sprache C bietet dem Programmierer eine gewisse Sicherheit hinsichtlich der Anordnung der Elemente in der Struktur:

  • Compiler, die eine Folge von Komponenten mit steigenden Speicheradressen zuweisen müssen
  • Die Adresse der ersten Komponente stimmt mit der Startadresse der Struktur überein
  • unbenannte Bitfelder können in die Struktur aufgenommen werden, um die erforderlichen Adressanpassungen benachbarter Elemente zu erreichen

Probleme im Zusammenhang mit der Ausrichtung der Elemente:

  • Verschiedene Computer zeichnen die Kanten von Objekten auf unterschiedliche Weise
  • Unterschiedliche Einschränkungen für die Breite des Bitfeldes
  • Computer unterscheiden sich darin, wie sie die Bytes in einem Wort speichern (Intel 80x86 und Motorola 68000)

Wie die Ausrichtung funktioniert:

  • Das von der Struktur eingenommene Volumen wird als die Größe des ausgerichteten Einzelelements einer Anordnung solcher Strukturen berechnet. Die Struktur sollte so enden, dass das erste Element der nächstfolgenden Struktur nicht gegen die Anforderungen der Ausrichtung verstößt

p.s. Ausführlichere Informationen finden Sie hier: "Samuel P.Harbison, Guy L.Steele C A Reference, (5.6.2 - 5.6.7)"

7voto

DigitalRoss Punkte 138823

Aus Geschwindigkeits- und Cachegründen sollten Operanden von Adressen gelesen werden, die an ihrer natürlichen Größe ausgerichtet sind. Um dies zu erreichen, füllt der Compiler Strukturmitglieder so auf, dass das folgende Mitglied oder die folgende Struktur ausgerichtet ist.

struct pixel {
    unsigned char red;   // 0
    unsigned char green; // 1
    unsigned int alpha;  // 4 (gotta skip to an aligned offset)
    unsigned char blue;  // 8 (then skip 9 10 11)
};

// next offset: 12

Die x86-Architektur war schon immer in der Lage, falsch ausgerichtete Adressen abzurufen. Dies ist jedoch langsamer, und wenn sich die Fehlausrichtung mit zwei verschiedenen Cache-Zeilen überschneidet, werden zwei Cache-Zeilen ausgelagert, während bei einem ausgerichteten Zugriff nur eine Zeile ausgelagert werden würde.

Einige Architekturen müssen bei falsch ausgerichteten Lese- und Schreibvorgängen Traps erstellen, und frühe Versionen der ARM-Architektur (aus der sich alle heutigen mobilen CPUs entwickelt haben) ... haben in diesen Fällen einfach schlechte Daten zurückgegeben. (Sie ignorierten die Bits niedriger Ordnung.)

Schließlich ist zu beachten, dass Cache-Zeilen beliebig groß sein können, und der Compiler versucht nicht, diese zu erraten oder einen Kompromiss zwischen Platzbedarf und Geschwindigkeit zu finden. Stattdessen sind die Ausrichtungsentscheidungen Teil der ABI und stellen die minimale Ausrichtung dar, die schließlich eine Cache-Zeile gleichmäßig auffüllt.

TL;DR: Ausrichtung ist wichtig.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X