# Pragma Pack Effekt

233

Ich habe mich gefragt, ob mir jemand erklären könnte, was die #pragma packPräprozessor-Anweisung bewirkt und was noch wichtiger ist, warum man sie verwenden möchte.

Ich habe mir die MSDN-Seite angesehen , die einige Einblicke bot, aber ich hatte gehofft, mehr von erfahrenen Leuten zu hören. Ich habe es schon einmal im Code gesehen, obwohl ich anscheinend nicht mehr finde, wo.

Cenoc
quelle
1
Es erzwingt eine bestimmte Ausrichtung / Packung einer Struktur, aber wie alle #pragmaAnweisungen sind sie implementierungsdefiniert.
Dreamlax
A mod s = 0wobei A die Adresse und s die Größe des Datentyps ist; Dies prüft, ob Daten nicht falsch ausgerichtet sind.
Legends2k

Antworten:

421

#pragma packWeist den Compiler an, Strukturelemente mit einer bestimmten Ausrichtung zu packen. Die meisten Compiler fügen beim Deklarieren einer Struktur einen Abstand zwischen den Elementen ein, um sicherzustellen, dass sie an den entsprechenden Adressen im Speicher ausgerichtet sind (normalerweise ein Vielfaches der Größe des Typs). Dies vermeidet die Leistungseinbußen (oder Fehler) bei einigen Architekturen, die mit dem Zugriff auf Variablen verbunden sind, die nicht richtig ausgerichtet sind. Beispiel: Geben Sie 4-Byte-Ganzzahlen und die folgende Struktur an:

struct Test
{
   char AA;
   int BB;
   char CC;
};

Der Compiler kann die Struktur wie folgt im Speicher anordnen:

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

und sizeof(Test)wäre 4 × 3 = 12, obwohl es nur 6 Datenbytes enthält. Der häufigste Anwendungsfall für #pragma(meines Wissens) ist die Arbeit mit Hardwaregeräten, bei denen Sie sicherstellen müssen, dass der Compiler keine Auffüllungen in die Daten einfügt und jedes Mitglied dem vorherigen folgt. Mit #pragma pack(1)würde die obige Struktur wie folgt aufgebaut sein:

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

Und sizeof(Test)wäre 1 × 6 = 6.

Mit #pragma pack(2)würde die obige Struktur wie folgt aufgebaut sein:

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

Und sizeof(Test)wäre 2 × 4 = 8.

Die Reihenfolge der Variablen in struct ist ebenfalls wichtig. Mit Variablen, die wie folgt geordnet sind:

struct Test
{
   char AA;
   char CC;
   int BB;
};

und mit #pragma pack(2)würde die Struktur so angelegt sein:

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

und sizeOf(Test)wäre 3 × 2 = 6.

Nick Meyer
quelle
76
Es könnte sich lohnen, die Nachteile der Verpackung hinzuzufügen. (Nicht ausgerichtete Objektzugriffe sind im besten Fall langsam , verursachen jedoch auf einigen Plattformen Fehler.)
Jalf
11
Scheint, dass die erwähnten Alignments "Leistungseinbußen" auf einigen Systemen tatsächlich von Vorteil sein könnten . Danluu.com/3c-conflict .
6
@ Pacerier Nicht wirklich. In diesem Beitrag geht es um eine ziemlich extreme Ausrichtung (Ausrichtung an 4-KB-Grenzen). Die CPU erwartet bestimmte Mindestausrichtungen für verschiedene Datentypen, diese erfordern jedoch im schlimmsten Fall eine 8-Byte-Ausrichtung (ohne Berücksichtigung von Vektortypen, für die möglicherweise eine 16- oder 32-Byte-Ausrichtung erforderlich ist). Wenn Sie sich nicht an diesen Grenzen ausrichten, erhalten Sie im Allgemeinen einen spürbaren Leistungseinbruch (da eine Last möglicherweise als zwei Operationen anstelle einer ausgeführt werden muss), aber der Typ ist entweder gut ausgerichtet oder nicht. Eine strengere Ausrichtung als das
bringt
6
Mit anderen Worten, ein Double erwartet eine 8-Byte-Grenze. Wenn Sie es auf eine 7-Byte-Grenze setzen, wird die Leistung beeinträchtigt. Wenn Sie es jedoch auf eine 16-, 32-, 64- oder 4096-Byte-Grenze setzen, erhalten Sie nichts über dem, was Ihnen die 8-Byte-Grenze bereits gegeben hat. Sie erhalten die gleiche Leistung von der CPU, während die Cache-Auslastung aus den in diesem Beitrag beschriebenen Gründen erheblich schlechter wird.
Jalf
4
Die Lektion lautet also nicht "Packen ist vorteilhaft" (Packen verletzt die natürliche Ausrichtung der Typen, so dass die Leistung
beeinträchtigt wird
27

#pragmawird verwendet, um nicht portierbare Nachrichten (wie nur in diesem Compiler) an den Compiler zu senden. Dinge wie das Deaktivieren bestimmter Warnungen und Verpackungsstrukturen sind häufige Gründe. Das Deaktivieren bestimmter Warnungen ist besonders nützlich, wenn Sie mit den Warnungen kompilieren, wenn das Fehlerflag aktiviert ist.

#pragma packwird speziell verwendet, um anzuzeigen, dass die Elemente der zu packenden Struktur nicht ausgerichtet sein sollten. Dies ist nützlich, wenn Sie eine speicherabgebildete Schnittstelle zu einer Hardware haben und genau steuern müssen, wohin die verschiedenen Strukturelemente zeigen. Dies ist insbesondere keine gute Geschwindigkeitsoptimierung, da die meisten Maschinen viel schneller mit ausgerichteten Daten umgehen können.

nmichaels
quelle
17
Um danach rückgängig zu machen, machen Sie dies: #pragma pack (push, 1) und #pragma pack (pop)
malhal
16

Es teilt dem Compiler die Grenze mit, an der Objekte in einer Struktur ausgerichtet werden sollen. Zum Beispiel, wenn ich so etwas habe wie:

struct foo { 
    char a;
    int b;
};

Bei einer typischen 32-Bit - Maschine, würden Sie normalerweise „wollen“ hat drei Bytes padding zwischen aund bso , dass bbei einer 4-Byte - Grenze landen wird seine Zugriffsgeschwindigkeit zu maximieren (und das ist , was typischerweise durch Standard passieren wird).

Wenn Sie jedoch mit einer extern definierten Struktur übereinstimmen müssen, möchten Sie sicherstellen, dass der Compiler Ihre Struktur genau gemäß dieser externen Definition auslegt. In diesem Fall können Sie den Compiler geben ein , #pragma pack(1)ihm zu sagen , nicht jede Polsterung zwischen den Mitgliedern einzusetzen - wenn die Definition der Struktur Polsterung zwischen den Mitgliedern enthält, können Sie es explizit einfügen (zB typischerweise mit Mitgliedern genannt unusedNoder ignoreN, oder etwas auf , dass Auftrag).

Jerry Sarg
quelle
"Normalerweise" möchten "Sie 3 Byte Auffüllen zwischen a und b, damit b an einer 4-Byte-Grenze landet, um die Zugriffsgeschwindigkeit zu maximieren" - wie würde ein Auffüllen mit 3 Byte die Zugriffsgeschwindigkeit maximieren?
Ashwin
8
@Ashwin: Das Platzieren ban einer 4-Byte-Grenze bedeutet, dass der Prozessor sie laden kann, indem er eine einzelne 4-Byte-Last ausgibt . Obwohl dies etwas vom Prozessor abhängt, besteht eine gute Chance, dass der Prozessor bei einer ungeraden Grenze zwei separate Ladeanweisungen ausgibt und diese Teile dann mit einem Shifter zusammenfügt. Eine typische Strafe liegt in der Größenordnung von 3x langsamerer Ladung dieses Gegenstands.
Jerry Coffin
... Wenn Sie sich den Assembly-Code zum Lesen von ausgerichtetem und nicht ausgerichtetem int ansehen, ist ausgerichtetes Lesen normalerweise eine einzelne Mnemonik. Nicht ausgerichteter Lesevorgang kann leicht aus 10 Zeilen zusammengesetzt werden, da der Int zusammengesetzt, Byte für Byte ausgewählt und an den richtigen Stellen des Registers platziert wird.
SF.
2
@SF.: Es kann sein - aber auch wenn dies nicht der Fall ist, lassen Sie sich nicht irreführen - auf einer x86-CPU (für ein offensichtliches Beispiel) werden die Operationen in Hardware ausgeführt, aber Sie erhalten immer noch ungefähr die gleichen Operationen und Verlangsamung.
Jerry Coffin
8

Datenelemente (z. B. Mitglieder von Klassen und Strukturen) werden normalerweise an WORD- oder DWORD-Grenzen für Prozessoren der aktuellen Generation ausgerichtet, um die Zugriffszeiten zu verbessern. Das Abrufen eines DWORD an einer Adresse, die nicht durch 4 teilbar ist, erfordert mindestens einen zusätzlichen CPU-Zyklus auf einem 32-Bit-Prozessor. Wenn Sie also z. B. drei Zeichenmitglieder haben char a, b, c;, benötigen diese tatsächlich 6 oder 12 Byte Speicherplatz.

#pragmaMit dieser Option können Sie diese überschreiben, um eine effizientere Speicherplatznutzung auf Kosten der Zugriffsgeschwindigkeit oder eine Konsistenz der gespeicherten Daten zwischen verschiedenen Compilerzielen zu erzielen. Ich hatte viel Spaß beim Übergang von 16-Bit- zu 32-Bit-Code. Ich gehe davon aus, dass die Portierung auf 64-Bit-Code bei einigen Codes die gleichen Kopfschmerzen verursacht.

Pontus Gagge
quelle
Tatsächlich char a,b,c;werden normalerweise entweder 3 oder 4 Bytes Speicher benötigt (mindestens auf x86) - das liegt daran, dass ihre Ausrichtungsanforderung 1 Byte beträgt. Wenn nicht, wie würden Sie dann damit umgehen char str[] = "foo";? Der Zugriff auf a charist immer eine einfache Fetch-Shift-Maske, während der Zugriff auf eine intFetch-Fetch-Merge oder nur Fetch erfolgen kann, je nachdem, ob sie ausgerichtet ist oder nicht. inthat (auf x86) eine 32-Bit-Ausrichtung (4 Byte), da Sie sonst inteine DWORDhalbe in der einen und eine halbe in der anderen erhalten würden, und das würde zwei Suchvorgänge erfordern.
Tim
3

Der Compiler kann Elemente in Strukturen ausrichten, um auf der bestimmten Plattform maximale Leistung zu erzielen. #pragma packMit der Direktive können Sie diese Ausrichtung steuern. Normalerweise sollten Sie es standardmäßig belassen, um eine optimale Leistung zu erzielen. Wenn Sie eine Struktur an den Remote-Computer übergeben müssen, werden Sie im Allgemeinen verwendet #pragma pack 1, um unerwünschte Ausrichtungen auszuschließen.

Kirill V. Lyadvinsky
quelle
2

Ein Compiler kann Strukturelemente aus Gründen der Leistung auf einer bestimmten Architektur an bestimmten Bytegrenzen platzieren. Dies kann zu unbenutzten Polstern zwischen den Mitgliedern führen. Die Strukturpackung zwingt die Elemente dazu, zusammenhängend zu sein.

Dies kann beispielsweise wichtig sein, wenn Sie eine Struktur benötigen, die einer bestimmten Datei oder einem bestimmten Kommunikationsformat entspricht, in dem sich die Daten, die Sie benötigen, an bestimmten Positionen innerhalb einer Sequenz befinden müssen. Eine solche Verwendung befasst sich jedoch nicht mit Endianitätsproblemen. Obwohl sie verwendet wird, ist sie möglicherweise nicht portabel.

Es kann auch sein, dass die interne Registerstruktur eines E / A-Geräts wie beispielsweise eines UART- oder USB-Controllers genau überlagert wird, damit der Registerzugriff über eine Struktur und nicht über direkte Adressen erfolgt.

Clifford
quelle
1

Sie möchten dies wahrscheinlich nur verwenden, wenn Sie auf einer Hardware (z. B. einem Gerät mit Speicherzuordnung) codieren, für die strenge Anforderungen an die Reihenfolge und Ausrichtung der Register gestellt wurden.

Dies scheint jedoch ein ziemlich stumpfes Werkzeug zu sein, um dieses Ziel zu erreichen. Ein besserer Ansatz wäre, einen Minitreiber in Assembler zu codieren und ihm eine C-Aufrufschnittstelle zu geben, anstatt mit diesem Pragma herumzufummeln.

msw
quelle
Ich benutze es tatsächlich ziemlich oft, um Platz in großen Tabellen zu sparen, auf die nicht häufig zugegriffen wird. Dort ist es nur platzsparend und nicht für eine strikte Ausrichtung. (Ich habe dich gerade gewählt, übrigens. Jemand hat dir eine negative Stimme gegeben.)
Todd Lehman
1

Ich habe es schon einmal im Code verwendet, allerdings nur als Schnittstelle zu Legacy-Code. Dies war eine Mac OS X Cocoa-Anwendung, die Voreinstellungsdateien aus einer früheren Carbon-Version laden musste (die selbst abwärtskompatibel mit der ursprünglichen M68k System 6.5-Version war ... Sie haben die Idee). Die Voreinstellungsdateien in der Originalversion waren ein binärer #pragma pack(1)Speicherauszug einer Konfigurationsstruktur, mit dem vermieden wurde, zusätzlichen Speicherplatz zu beanspruchen und Junk zu sparen (dh die Füllbytes, die sich sonst in der Struktur befinden würden).

Die ursprünglichen Autoren des Codes hatten auch #pragma pack(1)Strukturen gespeichert, die als Nachrichten in der Kommunikation zwischen Prozessen verwendet wurden. Ich denke, der Grund hier war, die Möglichkeit unbekannter oder geänderter Auffüllgrößen zu vermeiden, da der Code manchmal einen bestimmten Teil der Nachrichtenstruktur betrachtete, indem er von Anfang an eine Anzahl von Bytes zählte (ewww).


quelle
1

Ich habe gesehen, dass Leute es verwenden, um sicherzustellen, dass eine Struktur eine ganze Cache-Zeile benötigt, um eine falsche Freigabe in einem Multithread-Kontext zu verhindern. Wenn Sie eine große Anzahl von Objekten haben, die standardmäßig lose gepackt werden, kann dies Speicherplatz sparen und die Cache-Leistung verbessern, um sie enger zu packen, obwohl ein nicht ausgerichteter Speicherzugriff normalerweise die Dinge verlangsamt, sodass möglicherweise ein Nachteil auftritt.

Steinmetall
quelle
0

Beachten Sie, dass es andere Möglichkeiten gibt, Datenkonsistenz zu erreichen, die #pragma pack bietet (zum Beispiel verwenden einige Leute #pragma pack (1) für Strukturen, die über das Netzwerk gesendet werden sollen). Siehe zum Beispiel den folgenden Code und seine nachfolgende Ausgabe:

#include <stdio.h>

struct a {
    char one;
    char two[2];
    char eight[8];
    char four[4];
};

struct b { 
    char one;
    short two;
    long int eight;
    int four;
};

int main(int argc, char** argv) {
    struct a twoa[2] = {}; 
    struct b twob[2] = {}; 
    printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b));
    printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob));
}

Die Ausgabe ist wie folgt: sizeof (Struktur a): 15, sizeof (Struktur b): 24 sizeof (twoa): 30, sizeof (twob): 48

Beachten Sie, wie die Größe der Struktur a ist genau das, was die Byte - Zählung, aber struct b hat padding hinzugefügt (siehe diese Einzelheiten über die Polsterung). Auf diese Weise können Sie im Gegensatz zum # Pragma-Paket steuern, ob das "Drahtformat" in die entsprechenden Typen konvertiert wird. Zum Beispiel "char two [2]" in ein "short int" und so weiter.

Wangchow
quelle
Nein das ist falsch. Wenn Sie sich die Position im Speicher von b.two ansehen, ist es nicht ein Byte nach b.one (der Compiler kann (und wird oft) b.two so ausrichten, dass es auf den Wortzugriff ausgerichtet ist). Für a.two ist es genau ein Byte nach a.one. Wenn Sie als kurzes int auf a.two zugreifen müssen, sollten Sie zwei Alternativen haben: entweder eine Union verwenden (dies schlägt jedoch normalerweise fehl, wenn Sie ein Endianness-Problem haben) oder per Code entpacken / konvertieren (mit der entsprechenden ntohX-Funktion)
xryl669
1
sizeofgibt ein zurück, size_tdas mit ausgedruckt werden muss%zu . Die Verwendung des falschen Formatbezeichners ruft undefiniertes Verhalten hervor
phuclv
0

Warum will man es benutzen?

Den Speicher der Struktur reduzieren

Warum sollte man es nicht benutzen?

  1. Dies kann zu Leistungseinbußen führen, da einige Systeme bei ausgerichteten Daten besser funktionieren
  2. Einige Computer können nicht ausgerichtete Daten nicht lesen
  3. Code ist nicht portabel
VINOTH ENERGETIC
quelle