Anekdote: Es gab einen tatsächlichen Computervirus, der seinen Code in Strukturauffüllungen im Host-Programm eingefügt hat.
Elazar
4
@ Elzar Das ist beeindruckend! Ich hätte nie gedacht, dass es möglich ist, so kleine Bereiche für irgendetwas zu nutzen. Können Sie weitere Details angeben?
Dies liegt an der Auffüllung, die hinzugefügt wurde, um Ausrichtungsbeschränkungen zu erfüllen. Die Ausrichtung der Datenstruktur wirkt sich sowohl auf die Leistung als auch auf die Richtigkeit der Programme aus:
Ein falsch ausgerichteter Zugriff kann (häufig SIGBUS) ein schwerer Fehler sein .
Ein falsch ausgerichteter Zugriff kann ein weicher Fehler sein.
Entweder in der Hardware korrigiert, um eine leichte Leistungsverschlechterung zu erzielen.
Oder durch Emulation in Software korrigiert, um eine starke Leistungsverschlechterung zu erreichen.
Darüber hinaus können Atomizität und andere Parallelitätsgarantien verletzt werden, was zu subtilen Fehlern führt.
Hier ist ein Beispiel mit typischen Einstellungen für einen x86-Prozessor (alle verwendeten 32- und 64-Bit-Modi):
Man kann die Größe von Strukturen minimieren, indem man Elemente nach Ausrichtung sortiert (die Sortierung nach Größe reicht für die Basistypen aus) (wie die Struktur Zim obigen Beispiel).
WICHTIGER HINWEIS: Sowohl der C- als auch der C ++ - Standard geben an, dass die Strukturausrichtung implementierungsdefiniert ist. Daher kann jeder Compiler Daten unterschiedlich ausrichten, was zu unterschiedlichen und inkompatiblen Datenlayouts führt. Aus diesem Grund ist es beim Umgang mit Bibliotheken, die von verschiedenen Compilern verwendet werden, wichtig zu verstehen, wie die Compiler Daten ausrichten. Einige Compiler verfügen über Befehlszeileneinstellungen und / oder spezielle #pragmaAnweisungen, um die Einstellungen für die Strukturausrichtung zu ändern.
Ich möchte hier eine Notiz machen: Die meisten Prozessoren bestrafen Sie für nicht ausgerichteten Speicherzugriff (wie Sie erwähnt haben), aber Sie können nicht vergessen, dass viele dies völlig verbieten. Insbesondere die meisten MIPS-Chips lösen bei einem nicht ausgerichteten Zugriff eine Ausnahme aus.
Cody Brocious
35
Die x86-Chips sind insofern ziemlich einzigartig, als sie einen nicht ausgerichteten Zugriff ermöglichen, wenn auch bestraft. AFAIK die meisten Chips werfen Ausnahmen, nicht nur einige. PowerPC ist ein weiteres gängiges Beispiel.
Dark Shikari
6
Das Aktivieren von Pragmas für nicht ausgerichtete Zugriffe führt im Allgemeinen dazu, dass Ihr Code auf Prozessoren, die Fehlausrichtungsfehler auslösen, immer größer wird, da Code zum Beheben jeder Fehlausrichtung generiert werden muss. ARM löst auch Fehlausrichtungsfehler aus.
Mike Dimmick
5
@ Dark - stimme vollkommen zu. Aber die meisten Desktop-Prozessoren sind x86 / x64, so dass die meisten Chips keine Datenausrichtungsfehler verursachen;)
Aaron
27
Der nicht ausgerichtete Datenzugriff ist normalerweise eine Funktion in CISC-Architekturen, und die meisten RISC-Architekturen enthalten ihn nicht (ARM, MIPS, PowerPC, Cell). Tatsächlich sind die meisten Chips KEINE Desktop-Prozessoren, da die Anzahl der Chips eingebettet ist und die überwiegende Mehrheit davon RISC-Architekturen sind.
Es ist für die Ausrichtung. Viele Prozessoren können nicht auf 2- und 4-Byte-Mengen zugreifen (z. B. Ints und Long-Ints), wenn sie in jeder Hinsicht überfüllt sind.
Nun könnte man denken, dass es möglich sein sollte, diese Struktur wie folgt in den Speicher zu packen:
+-------+-------+-------+-------+| a | b |+-------+-------+-------+-------+| b | c |+-------+-------+-------+-------+| c | d |+-------+-------+-------+-------+
Aber es ist viel, viel einfacher für den Prozessor, wenn der Compiler es so anordnet:
+-------+-------+-------+| a |+-------+-------+-------+| b |+-------+-------+-------+-------+| c |+-------+-------+-------+-------+| d |+-------+-------+-------+
Beachten Sie in der gepackten Version, dass es für Sie und mich zumindest ein bisschen schwierig ist zu sehen, wie sich die Felder b und c drehen? Kurz gesagt, es ist auch für den Prozessor schwierig. Daher füllen die meisten Compiler die Struktur (wie mit zusätzlichen, unsichtbaren Feldern) wie folgt auf:
+-------+-------+-------+-------+| a | pad1 |+-------+-------+-------+-------+| b | pad2 |+-------+-------+-------+-------+| c |+-------+-------+-------+-------+| d | pad3 |+-------+-------+-------+-------+
@EmmEff das mag falsch sein, aber ich verstehe es nicht ganz: Warum gibt es keinen Speicherplatz für den Zeiger in den Arrays?
Balázs Börcsök
1
@ BalázsBörcsök Dies sind Arrays mit konstanter Größe, und daher werden ihre Elemente mit festen Offsets direkt in der Struktur gespeichert. Der Compiler weiß dies alles zur Kompilierungszeit, sodass der Zeiger implizit ist. Wenn Sie beispielsweise eine Strukturvariable dieses Typs mit dem Namen sthen &s.a == &sund haben &s.d == &s + 12(angesichts der in der Antwort gezeigten Ausrichtung). Der Zeiger wird nur gespeichert, wenn die Arrays eine variable Größe haben (z. B. awurde char a[]anstelle von deklariert char a[3]), aber dann müssen die Elemente an einer anderen Stelle gespeichert werden.
Kbolino
27
Wenn Sie möchten, dass die Struktur beispielsweise mit GCC eine bestimmte Größe hat, verwenden Sie __attribute__((packed)) .
Unter Windows können Sie die Ausrichtung auf ein Byte festlegen, wenn Sie den cl.exe-Compier mit der Option / Zp verwenden .
Normalerweise ist es für die CPU einfacher, auf Daten zuzugreifen, die ein Vielfaches von 4 (oder 8) sind, abhängig von der Plattform und auch vom Compiler.
"gute Gründe" Beispiel: Gleichbleibende Binärkompatibilität (Padding) zwischen 32-Bit- und 64-Bit-Systemen für eine komplexe Struktur im Proof-of-Concept-Demo-Code, der morgen vorgestellt wird. Manchmal muss die Notwendigkeit Vorrang vor der Angemessenheit haben.
Mr.Ree
2
Alles ist in Ordnung, außer wenn Sie das Betriebssystem erwähnen. Dies ist ein Problem für die CPU-Geschwindigkeit, das Betriebssystem ist überhaupt nicht beteiligt.
Blaisorblade
3
Ein weiterer guter Grund ist, wenn Sie einen Datenstrom in eine Struktur einfügen, z. B. beim Parsen von Netzwerkprotokollen.
CEO
1
@dolmen Ich habe gerade darauf hingewiesen, dass "es für das Betriebssystem einfacher ist, auf Daten zuzugreifen" falsch ist, da das Betriebssystem nicht auf Daten zugreift.
Blaisorblade
1
@dolmen In der Tat sollte man über die ABI (Application Binary Interface) sprechen. Die Standardausrichtung (wird verwendet, wenn Sie sie in der Quelle nicht ändern) hängt vom ABI ab. Viele Betriebssysteme unterstützen mehrere ABIs (z. B. 32- und 64-Bit oder für Binärdateien von verschiedenen Betriebssystemen oder für verschiedene Arten des Kompilierens des gleiche Binärdateien für dasselbe Betriebssystem). OTOH, welche Ausrichtung in Bezug auf die Leistung zweckmäßig ist, hängt von der CPU ab. Auf den Speicher wird auf die gleiche Weise zugegriffen, unabhängig davon, ob Sie den 32- oder 64-Bit-Modus verwenden (ich kann den Real-Modus nicht kommentieren, scheint aber heutzutage für die Leistung kaum relevant zu sein). IIRC Pentium begann, eine 8-Byte-Ausrichtung zu bevorzugen.
Blaisorblade
15
Dies kann auf die Ausrichtung und das Auffüllen von Bytes zurückzuführen sein, sodass die Struktur eine gerade Anzahl von Bytes (oder Wörtern) auf Ihrer Plattform aufweist. Zum Beispiel in C unter Linux die folgenden 3 Strukturen:
Haben Sie Mitglieder, deren Größe (in Bytes) 4 Bytes (32 Bit), 8 Bytes (2x 32 Bit) bzw. 1 Byte (2 + 6 Bit) beträgt. Das obige Programm (unter Linux mit gcc) gibt die Größen 4, 8 und 4 aus - wobei die letzte Struktur so aufgefüllt wird, dass es sich um ein einzelnes Wort handelt (4 x 8-Bit-Bytes auf meiner 32-Bit-Plattform).
"C unter Linux mit gcc" reicht nicht aus, um Ihre Plattform zu beschreiben. Die Ausrichtung hängt hauptsächlich von der CPU-Architektur ab.
Dolmen
- @ Kyle Burton. Entschuldigung, ich verstehe nicht, warum die Größe der Struktur "someBits" gleich 4 ist. Ich erwarte 8 Bytes, da 2 Ganzzahlen deklariert sind (2 * sizeof (int)) = 8 Bytes. danke
youpilat13
1
Hi @ youpilat13, die :2und geben :6tatsächlich 2 und 6 Bit an, in diesem Fall keine vollen 32-Bit-Ganzzahlen. Einige Bits.x, die nur 2 Bits sind, können nur 4 mögliche Werte speichern: 00, 01, 10 und 11 (1, 2, 3 und 4). Macht das Sinn? Hier ist ein Artikel über die Funktion: geeksforgeeks.org/bit-fields-c
Beachten Sie zusätzlich zu den vorherigen Antworten, dass es in C ++ unabhängig von der Verpackung keine Garantie für die Bestellung von Mitgliedern gibt . Compiler können (und sicherlich auch) Mitglieder der virtuellen Tabellenzeiger und Basisstrukturen zur Struktur hinzufügen. Selbst die Existenz einer virtuellen Tabelle wird durch den Standard nicht sichergestellt (die Implementierung eines virtuellen Mechanismus ist nicht spezifiziert), und daher kann man schließen, dass eine solche Garantie einfach unmöglich ist.
Ich bin ganz sicher , Mitglied Ordnung ist in C garantiert , aber ich würde nicht auf sie zählen, wenn eine plattformübergreifende oder Cross-Compiler - Programm zu schreiben.
"Ich bin mir ziemlich sicher, dass die Reihenfolge der Mitglieder in C grunzt". Ja, C99 sagt: "Innerhalb eines Strukturobjekts haben die Nicht-Bitfeld-Mitglieder und die Einheiten, in denen sich Bitfelder befinden, Adressen, die in der Reihenfolge zunehmen, in der sie deklariert werden." Weitere Standardgüte unter: stackoverflow.com/a/37032302/895245
Die Größe einer Struktur ist aufgrund der sogenannten Packung größer als die Summe ihrer Teile. Ein bestimmter Prozessor hat eine bevorzugte Datengröße, mit der er arbeitet. Die bevorzugte Größe der meisten modernen Prozessoren beträgt 32 Bit (4 Byte). Der Zugriff auf den Speicher, wenn sich Daten an dieser Art von Grenze befinden, ist effizienter als Dinge, die diese Größengrenze überschreiten.
Zum Beispiel. Betrachten Sie die einfache Struktur:
struct myStruct
{int a;char b;int c;} data;
Wenn die Maschine eine 32-Bit-Maschine ist und Daten an einer 32-Bit-Grenze ausgerichtet sind, sehen wir ein unmittelbares Problem (vorausgesetzt, keine Strukturausrichtung). In diesem Beispiel nehmen wir an, dass die Strukturdaten an der Adresse 1024 beginnen (0x400 - beachten Sie, dass die niedrigsten 2 Bits Null sind, sodass die Daten an einer 32-Bit-Grenze ausgerichtet sind). Der Zugriff auf data.a funktioniert einwandfrei, da er an einer Grenze beginnt - 0x400. Der Zugriff auf data.b funktioniert ebenfalls einwandfrei, da er sich an der Adresse 0x404 befindet - einer weiteren 32-Bit-Grenze. Eine nicht ausgerichtete Struktur würde jedoch data.c an die Adresse 0x405 setzen. Die 4 Bytes von data.c befinden sich bei 0x405, 0x406, 0x407, 0x408. Auf einer 32-Bit-Maschine würde das System während eines Speicherzyklus data.c lesen, aber nur 3 der 4 Bytes erhalten (das 4. Byte befindet sich an der nächsten Grenze). Das System müsste also einen zweiten Speicherzugriff durchführen, um das 4. Byte zu erhalten.
Wenn der Compiler nun anstelle von data.c an der Adresse 0x405 die Struktur mit 3 Bytes auffüllt und data.c an der Adresse 0x408 platziert, benötigt das System nur 1 Zyklus zum Lesen der Daten, wodurch die Zugriffszeit auf dieses Datenelement verkürzt wird um 50%. Durch das Auffüllen wird die Speichereffizienz gegen die Verarbeitungseffizienz ausgetauscht. Angesichts der Tatsache, dass Computer sehr viel Speicher haben können (viele Gigabyte), halten die Compiler den Austausch (Geschwindigkeit über Größe) für angemessen.
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 Auffüllen zwischen Elementen einer Struktur oder Klasse kann die an die Datei oder das Netzwerk gesendeten Daten stören. Um tragbaren Code zu schreiben (einer, der an mehrere verschiedene Compiler gesendet wird), müssen Sie wahrscheinlich auf jedes Element der Struktur separat zugreifen, um das richtige "Packen" sicherzustellen.
Andererseits haben verschiedene Compiler unterschiedliche Fähigkeiten, um das Packen von Datenstrukturen zu verwalten. In Visual C / C ++ unterstützt der Compiler beispielsweise den Befehl #pragma pack. Auf diese Weise können Sie das Packen und Ausrichten von Daten anpassen.
Zum Beispiel:
#pragma pack 1structMyStruct{int a;char b;int c;short d;} myData;
I =sizeof(myData);
Ich sollte jetzt die Länge von 11 haben. Ohne das Pragma könnte ich zwischen 11 und 14 sein (und für einige Systeme sogar 32), abhängig von der Standardverpackung des Compilers.
Dies diskutiert die Konsequenzen der Strukturauffüllung, beantwortet jedoch nicht die Frage.
Keith Thompson
" ... wegen dem, was als Packen bezeichnet wird. ... - Ich denke, Sie meinen" Auffüllen "." Die bevorzugte Größe der meisten modernen Prozessoren bei 32 Bit (4 Byte) "- Das ist ein bisschen zu einfach. Normalerweise Größen von 8, 16, 32 und 64 Bit werden unterstützt, häufig hat jede Größe ihre eigene Ausrichtung. Und ich bin nicht sicher, ob Ihre Antwort neue Informationen hinzufügt, die nicht bereits in der akzeptierten Antwort enthalten sind.
Keith Thompson
1
Als ich Packen sagte, meinte ich, wie der Compiler Daten in eine Struktur packt (und dies kann durch Auffüllen der kleinen Elemente geschehen, muss aber nicht aufgefüllt werden, sondern packt immer). Was die Größe betrifft - ich habe über die Systemarchitektur gesprochen, nicht darüber, was das System für den Datenzugriff unterstützt (was sich stark von der zugrunde liegenden Busarchitektur unterscheidet). Was Ihren letzten Kommentar betrifft, habe ich einen Aspekt des Kompromisses (Geschwindigkeit gegenüber Größe) vereinfacht und erweitert - ein großes Programmierproblem. Ich beschreibe auch einen Weg, um das Problem zu beheben - das war nicht in der akzeptierten Antwort.
Sid1138
"Packen" bezieht sich in diesem Zusammenhang normalerweise auf eine engere Zuordnung von Mitgliedern als die Standardeinstellung, wie bei #pragma pack. Wenn Mitglieder bei ihrer Standardausrichtung zugewiesen werden, würde ich im Allgemeinen sagen, dass die Struktur nicht gepackt ist.
Keith Thompson
Verpackung ist eine Art überladener Begriff. Es bedeutet, wie Sie Strukturelemente in den Speicher einfügen. Ähnlich wie beim Einlegen von Gegenständen in eine Kiste (Verpackung zum Bewegen). Es bedeutet auch, Elemente ohne Polsterung in den Speicher zu bringen (eine Art kurze Hand für "dicht gepackt"). Dann gibt es die Befehlsversion des Wortes im Befehl #pragma pack.
Sid1138
5
Dies ist möglich, wenn Sie die Ausrichtung der Struktur implizit oder explizit festgelegt haben. Eine Struktur, die auf 4 ausgerichtet ist, ist immer ein Vielfaches von 4 Bytes, selbst wenn die Größe ihrer Mitglieder etwas ist, das kein Vielfaches von 4 Bytes ist.
Außerdem kann eine Bibliothek unter x86 mit 32-Bit-Ints kompiliert werden, und wenn Sie ihre Komponenten in einem 64-Bit-Prozess vergleichen, erhalten Sie ein anderes Ergebnis, wenn Sie dies von Hand tun.
3 Bei Anwendung auf einen Operanden mit Struktur- oder Vereinigungstyp ergibt sich die Gesamtzahl der Bytes in einem solchen Objekt, einschließlich interner und nachfolgender Auffüllung.
6.7.2.1 Struktur- und Vereinigungsspezifizierer :
13 ... In einem Strukturobjekt befindet sich möglicherweise eine unbenannte Auffüllung, jedoch nicht am Anfang.
und:
15 Am Ende einer Struktur oder Vereinigung befindet sich möglicherweise eine unbenannte Polsterung.
Die neue C99-Funktion für flexible Array-Elemente ( struct S {int is[];};) kann sich auch auf das Auffüllen auswirken:
16 Als Sonderfall kann das letzte Element einer Struktur mit mehr als einem benannten Element 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-Element weggelassen worden wäre, mit der Ausnahme, dass es möglicherweise mehr nachlaufende Polsterung aufweist, als die Auslassung implizieren würde.
Anhang J Portabilitätsprobleme bekräftigt:
Folgendes ist nicht spezifiziert: ...
Der Wert des Auffüllens von Bytes beim Speichern von Werten in Strukturen oder Vereinigungen (6.2.6.1)
2 Bei Anwendung auf eine Klasse ergibt sich die Anzahl der Bytes in einem Objekt dieser Klasse, einschließlich aller Auffüllungen, die zum Platzieren von Objekten dieses Typs in einem Array erforderlich sind.
9.2 Klassenmitglieder :
Ein Zeiger auf ein Strukturobjekt mit Standardlayout, das mit einem reinterpret_cast geeignet konvertiert wurde, zeigt auf sein ursprüngliches Element (oder, wenn dieses Element ein Bitfeld ist, auf die Einheit, in der es sich befindet) und umgekehrt. [Hinweis: In einem Strukturobjekt mit Standardlayout kann es daher zu unbenannten Auffüllungen kommen, jedoch nicht zu Beginn, um eine angemessene Ausrichtung zu erreichen. - Endnote]
Ich kenne nur genug C ++, um den Hinweis zu verstehen :-)
Zusätzlich zu den anderen Antworten kann eine Struktur virtuelle Funktionen haben (aber normalerweise nicht). In diesem Fall enthält die Größe der Struktur auch den Platz für das vtbl.
Nicht ganz. In typischen Implementierungen wird der Struktur ein vtable- Zeiger hinzugefügt .
Don Wakefield
3
Die Sprache C lässt dem Compiler einige Freiheiten hinsichtlich der Position der Strukturelemente im Speicher:
Zwischen zwei beliebigen Komponenten und nach der letzten Komponente können Speicherlöcher auftreten. Dies lag an der Tatsache, dass bestimmte Arten von Objekten auf dem Zielcomputer durch die Adressierungsgrenzen begrenzt sein können
Die Größe der "Speicherlöcher" ist im Ergebnis der Größe des Operators enthalten. Die Größe von enthält nur nicht die Größe des flexiblen Arrays, das in C / C ++ verfügbar ist
Bei einigen Implementierungen der Sprache können Sie das Speicherlayout von Strukturen über die Optionen Pragma und Compiler steuern
Die C-Sprache bietet dem Programmierer eine gewisse Sicherheit für das Elementlayout in der Struktur:
Compiler, die erforderlich sind, um eine Folge von Komponenten zuzuweisen, die die Speicheradressen erhöhen
Die Adresse der ersten Komponente stimmt mit der Startadresse der Struktur überein
unbenannte Bitfelder können in der Struktur zu den erforderlichen Adressausrichtungen benachbarter Elemente enthalten sein
Probleme im Zusammenhang mit der Ausrichtung der Elemente:
Verschiedene Computer säumen die Kanten von Objekten auf unterschiedliche Weise
Unterschiedliche Einschränkungen der Breite des Bitfeldes
Computer unterscheiden sich darin, wie die Bytes in einem Wort gespeichert werden (Intel 80x86 und Motorola 68000).
So funktioniert die Ausrichtung:
Das von der Struktur eingenommene Volumen wird als Größe des ausgerichteten Einzelelements eines Arrays solcher Strukturen berechnet. Die Struktur sollte so enden, dass das erste Element der nächsten folgenden Struktur die Ausrichtungsanforderungen nicht verletzt
ps Ausführlichere Informationen finden Sie hier: "Samuel P. Harbison, Guy L. Steele CA Reference, (5.6.2 - 5.6.7)"
Die Idee ist, dass Operanden aus Gründen der Geschwindigkeit und des Cache von Adressen gelesen werden sollten, die auf ihre natürliche Größe ausgerichtet sind. Um dies zu erreichen, füllt der Compiler die Strukturelemente so auf, dass das folgende Element oder die folgende Struktur ausgerichtet werden.
struct pixel {unsignedchar red;// 0unsignedchar green;// 1unsignedint alpha;// 4 (gotta skip to an aligned offset)unsignedchar blue;// 8 (then skip 9 10 11)};// next offset: 12
Die x86-Architektur war immer in der Lage, falsch ausgerichtete Adressen abzurufen. Es ist jedoch langsamer und wenn die Fehlausrichtung zwei verschiedene Cache-Zeilen überlappt, werden zwei Cache-Zeilen entfernt, wenn ein ausgerichteter Zugriff nur eine entfernen würde.
Einige Architekturen müssen sich tatsächlich auf falsch ausgerichtete Lese- und Schreibvorgänge und frühe Versionen der ARM-Architektur (die sich zu allen heutigen mobilen CPUs entwickelt hat) beschränken. Nun, sie haben tatsächlich nur schlechte Daten für diese zurückgegeben. (Sie ignorierten die niederwertigen Bits.)
Beachten Sie schließlich, dass die Cache-Zeilen beliebig groß sein können und der Compiler nicht versucht, diese zu erraten oder einen Kompromiss zwischen Leerzeichen und Geschwindigkeit einzugehen. Stattdessen sind die Ausrichtungsentscheidungen Teil des ABI und stellen die minimale Ausrichtung dar, die schließlich eine Cache-Zeile gleichmäßig ausfüllt.
Antworten:
Dies liegt an der Auffüllung, die hinzugefügt wurde, um Ausrichtungsbeschränkungen zu erfüllen. Die Ausrichtung der Datenstruktur wirkt sich sowohl auf die Leistung als auch auf die Richtigkeit der Programme aus:
SIGBUS
) ein schwerer Fehler sein .Hier ist ein Beispiel mit typischen Einstellungen für einen x86-Prozessor (alle verwendeten 32- und 64-Bit-Modi):
Man kann die Größe von Strukturen minimieren, indem man Elemente nach Ausrichtung sortiert (die Sortierung nach Größe reicht für die Basistypen aus) (wie die Struktur
Z
im obigen Beispiel).WICHTIGER HINWEIS: Sowohl der C- als auch der C ++ - Standard geben an, dass die Strukturausrichtung implementierungsdefiniert ist. Daher kann jeder Compiler Daten unterschiedlich ausrichten, was zu unterschiedlichen und inkompatiblen Datenlayouts führt. Aus diesem Grund ist es beim Umgang mit Bibliotheken, die von verschiedenen Compilern verwendet werden, wichtig zu verstehen, wie die Compiler Daten ausrichten. Einige Compiler verfügen über Befehlszeileneinstellungen und / oder spezielle
#pragma
Anweisungen, um die Einstellungen für die Strukturausrichtung zu ändern.quelle
Packen und Byte-Alignment, wie in den C-FAQ hier beschrieben :
quelle
s
then&s.a == &s
und haben&s.d == &s + 12
(angesichts der in der Antwort gezeigten Ausrichtung). Der Zeiger wird nur gespeichert, wenn die Arrays eine variable Größe haben (z. B.a
wurdechar a[]
anstelle von deklariertchar a[3]
), aber dann müssen die Elemente an einer anderen Stelle gespeichert werden.Wenn Sie möchten, dass die Struktur beispielsweise mit GCC eine bestimmte Größe hat, verwenden Sie
__attribute__((packed))
.Unter Windows können Sie die Ausrichtung auf ein Byte festlegen, wenn Sie den cl.exe-Compier mit der Option / Zp verwenden .
Normalerweise ist es für die CPU einfacher, auf Daten zuzugreifen, die ein Vielfaches von 4 (oder 8) sind, abhängig von der Plattform und auch vom Compiler.
Es ist also im Grunde eine Frage der Ausrichtung.
Sie müssen gute Gründe haben, dies zu ändern.
quelle
Dies kann auf die Ausrichtung und das Auffüllen von Bytes zurückzuführen sein, sodass die Struktur eine gerade Anzahl von Bytes (oder Wörtern) auf Ihrer Plattform aufweist. Zum Beispiel in C unter Linux die folgenden 3 Strukturen:
Haben Sie Mitglieder, deren Größe (in Bytes) 4 Bytes (32 Bit), 8 Bytes (2x 32 Bit) bzw. 1 Byte (2 + 6 Bit) beträgt. Das obige Programm (unter Linux mit gcc) gibt die Größen 4, 8 und 4 aus - wobei die letzte Struktur so aufgefüllt wird, dass es sich um ein einzelnes Wort handelt (4 x 8-Bit-Bytes auf meiner 32-Bit-Plattform).
quelle
:2
und geben:6
tatsächlich 2 und 6 Bit an, in diesem Fall keine vollen 32-Bit-Ganzzahlen. Einige Bits.x, die nur 2 Bits sind, können nur 4 mögliche Werte speichern: 00, 01, 10 und 11 (1, 2, 3 und 4). Macht das Sinn? Hier ist ein Artikel über die Funktion: geeksforgeeks.org/bit-fields-cSiehe auch:
für Microsoft Visual C:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
und GCC behaupten Kompatibilität mit dem Compiler von Microsoft:
http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
Beachten Sie zusätzlich zu den vorherigen Antworten, dass es in C ++ unabhängig von der Verpackung keine Garantie für die Bestellung von Mitgliedern gibt . Compiler können (und sicherlich auch) Mitglieder der virtuellen Tabellenzeiger und Basisstrukturen zur Struktur hinzufügen. Selbst die Existenz einer virtuellen Tabelle wird durch den Standard nicht sichergestellt (die Implementierung eines virtuellen Mechanismus ist nicht spezifiziert), und daher kann man schließen, dass eine solche Garantie einfach unmöglich ist.
Ich bin ganz sicher , Mitglied Ordnung ist in C garantiert , aber ich würde nicht auf sie zählen, wenn eine plattformübergreifende oder Cross-Compiler - Programm zu schreiben.
quelle
Die Größe einer Struktur ist aufgrund der sogenannten Packung größer als die Summe ihrer Teile. Ein bestimmter Prozessor hat eine bevorzugte Datengröße, mit der er arbeitet. Die bevorzugte Größe der meisten modernen Prozessoren beträgt 32 Bit (4 Byte). Der Zugriff auf den Speicher, wenn sich Daten an dieser Art von Grenze befinden, ist effizienter als Dinge, die diese Größengrenze überschreiten.
Zum Beispiel. Betrachten Sie die einfache Struktur:
Wenn die Maschine eine 32-Bit-Maschine ist und Daten an einer 32-Bit-Grenze ausgerichtet sind, sehen wir ein unmittelbares Problem (vorausgesetzt, keine Strukturausrichtung). In diesem Beispiel nehmen wir an, dass die Strukturdaten an der Adresse 1024 beginnen (0x400 - beachten Sie, dass die niedrigsten 2 Bits Null sind, sodass die Daten an einer 32-Bit-Grenze ausgerichtet sind). Der Zugriff auf data.a funktioniert einwandfrei, da er an einer Grenze beginnt - 0x400. Der Zugriff auf data.b funktioniert ebenfalls einwandfrei, da er sich an der Adresse 0x404 befindet - einer weiteren 32-Bit-Grenze. Eine nicht ausgerichtete Struktur würde jedoch data.c an die Adresse 0x405 setzen. Die 4 Bytes von data.c befinden sich bei 0x405, 0x406, 0x407, 0x408. Auf einer 32-Bit-Maschine würde das System während eines Speicherzyklus data.c lesen, aber nur 3 der 4 Bytes erhalten (das 4. Byte befindet sich an der nächsten Grenze). Das System müsste also einen zweiten Speicherzugriff durchführen, um das 4. Byte zu erhalten.
Wenn der Compiler nun anstelle von data.c an der Adresse 0x405 die Struktur mit 3 Bytes auffüllt und data.c an der Adresse 0x408 platziert, benötigt das System nur 1 Zyklus zum Lesen der Daten, wodurch die Zugriffszeit auf dieses Datenelement verkürzt wird um 50%. Durch das Auffüllen wird die Speichereffizienz gegen die Verarbeitungseffizienz ausgetauscht. Angesichts der Tatsache, dass Computer sehr viel Speicher haben können (viele Gigabyte), halten die Compiler den Austausch (Geschwindigkeit über Größe) für angemessen.
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 Auffüllen zwischen Elementen einer Struktur oder Klasse kann die an die Datei oder das Netzwerk gesendeten Daten stören. Um tragbaren Code zu schreiben (einer, der an mehrere verschiedene Compiler gesendet wird), müssen Sie wahrscheinlich auf jedes Element der Struktur separat zugreifen, um das richtige "Packen" sicherzustellen.
Andererseits haben verschiedene Compiler unterschiedliche Fähigkeiten, um das Packen von Datenstrukturen zu verwalten. In Visual C / C ++ unterstützt der Compiler beispielsweise den Befehl #pragma pack. Auf diese Weise können Sie das Packen und Ausrichten von Daten anpassen.
Zum Beispiel:
Ich sollte jetzt die Länge von 11 haben. Ohne das Pragma könnte ich zwischen 11 und 14 sein (und für einige Systeme sogar 32), abhängig von der Standardverpackung des Compilers.
quelle
#pragma pack
. Wenn Mitglieder bei ihrer Standardausrichtung zugewiesen werden, würde ich im Allgemeinen sagen, dass die Struktur nicht gepackt ist.Dies ist möglich, wenn Sie die Ausrichtung der Struktur implizit oder explizit festgelegt haben. Eine Struktur, die auf 4 ausgerichtet ist, ist immer ein Vielfaches von 4 Bytes, selbst wenn die Größe ihrer Mitglieder etwas ist, das kein Vielfaches von 4 Bytes ist.
Außerdem kann eine Bibliothek unter x86 mit 32-Bit-Ints kompiliert werden, und wenn Sie ihre Komponenten in einem 64-Bit-Prozess vergleichen, erhalten Sie ein anderes Ergebnis, wenn Sie dies von Hand tun.
quelle
C99 N1256 Standardentwurf
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 Die Größe des Operators :
6.7.2.1 Struktur- und Vereinigungsspezifizierer :
und:
Die neue C99-Funktion für flexible Array-Elemente (
struct S {int is[];};
) kann sich auch auf das Auffüllen auswirken:Anhang J Portabilitätsprobleme bekräftigt:
C ++ 11 N3337 Standardentwurf
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3 Größe von :
9.2 Klassenmitglieder :
Ich kenne nur genug C ++, um den Hinweis zu verstehen :-)
quelle
Zusätzlich zu den anderen Antworten kann eine Struktur virtuelle Funktionen haben (aber normalerweise nicht). In diesem Fall enthält die Größe der Struktur auch den Platz für das vtbl.
quelle
Die Sprache C lässt dem Compiler einige Freiheiten hinsichtlich der Position der Strukturelemente im Speicher:
Die C-Sprache bietet dem Programmierer eine gewisse Sicherheit für das Elementlayout in der Struktur:
Probleme im Zusammenhang mit der Ausrichtung der Elemente:
So funktioniert die Ausrichtung:
ps Ausführlichere Informationen finden Sie hier: "Samuel P. Harbison, Guy L. Steele CA Reference, (5.6.2 - 5.6.7)"
quelle
Die Idee ist, dass Operanden aus Gründen der Geschwindigkeit und des Cache von Adressen gelesen werden sollten, die auf ihre natürliche Größe ausgerichtet sind. Um dies zu erreichen, füllt der Compiler die Strukturelemente so auf, dass das folgende Element oder die folgende Struktur ausgerichtet werden.
Die x86-Architektur war immer in der Lage, falsch ausgerichtete Adressen abzurufen. Es ist jedoch langsamer und wenn die Fehlausrichtung zwei verschiedene Cache-Zeilen überlappt, werden zwei Cache-Zeilen entfernt, wenn ein ausgerichteter Zugriff nur eine entfernen würde.
Einige Architekturen müssen sich tatsächlich auf falsch ausgerichtete Lese- und Schreibvorgänge und frühe Versionen der ARM-Architektur (die sich zu allen heutigen mobilen CPUs entwickelt hat) beschränken. Nun, sie haben tatsächlich nur schlechte Daten für diese zurückgegeben. (Sie ignorierten die niederwertigen Bits.)
Beachten Sie schließlich, dass die Cache-Zeilen beliebig groß sein können und der Compiler nicht versucht, diese zu erraten oder einen Kompromiss zwischen Leerzeichen und Geschwindigkeit einzugehen. Stattdessen sind die Ausrichtungsentscheidungen Teil des ABI und stellen die minimale Ausrichtung dar, die schließlich eine Cache-Zeile gleichmäßig ausfüllt.
TL; DR: Ausrichtung ist wichtig.
quelle