2010-10-12 10 views
6

Auf meine Frage, wollen wir klären mit einem Beispielprogramm beginnen:Was macht VC++ beim Packen von Bitfeldern?

#include <stdio.h> 

#pragma pack(push,1) 
struct cc { 
    unsigned int a : 3; 
    unsigned int b : 16; 
    unsigned int c : 1; 
    unsigned int d : 1; 
    unsigned int e : 1; 
    unsigned int f : 1; 
    unsigned int g : 1; 
    unsigned int h : 1; 
    unsigned int i : 6; 
    unsigned int j : 6; 
    unsigned int k : 4; 
    unsigned int l : 15; 
}; 
#pragma pack(pop) 

struct cc c; 

int main(int argc, char **argv) 

{ printf("%d\n",sizeof(c)); 
} 

Der Ausgang „8“ ist, was bedeutet, dass die 56 Bits (7 Byte) Ich möchte in 8 Byte gepackt werden packen, scheinbar ein ganzes Byte verschwenden. Neugierig, wie der Compiler diese Bits aus im Speicher lag, habe ich versucht, bestimmte Werte zu &c schreiben, zB:

int main (int argc, char ** argv)

{ 
unsigned long long int* pint = &c; 
*pint = 0xFFFFFFFF; 
printf("c.a = %d", c.a); 
... 
printf("c.l = %d", c.l); 
} 

Vorhersagbar auf x86_64 mit Visual Studio 2010, passiert folgendes:

*pint = 0x00000000 000000FF : 

c[0].a = 7 
c[0].b = 1 
c[0].c = 1 
c[0].d = 1 
c[0].e = 1 
c[0].f = 1 
c[0].g = 0 
c[0].h = 0 
c[0].i = 0 
c[0].j = 0 
c[0].k = 0 
c[0].l = 0 

*pint = 0x00000000 0000FF00 : 

c[0].a = 0 
c[0].b = 0 
c[0].c = 0 
c[0].d = 0 
c[0].e = 0 
c[0].f = 0 
c[0].g = 1 
c[0].h = 127 
c[0].i = 0 
c[0].j = 0 
c[0].k = 0 
c[0].l = 0 


*pint = 0x00000000 00FF0000 : 

c[0].a = 0 
c[0].b = 0 
c[0].c = 0 
c[0].d = 0 
c[0].e = 0 
c[0].f = 0 
c[0].g = 0 
c[0].h = 32640 
c[0].i = 0 
c[0].j = 0 
c[0].k = 0 
c[0].l = 0 

usw.

Vergessen Portabilität für einen Moment und annehmen, dass Sie über eine CPU, einen Compiler Pflege und eine Laufzeitumgebung. Warum kann VC++ diese Struktur nicht in 7 Bytes packen? Ist es eine Wortlänge? Die MSDN docs on #pragma pack sagt "die Ausrichtung eines Elements wird auf einer Grenze, die entweder ein Vielfaches von n [1 in meinem Fall] oder ein Vielfaches der Größe des Elements ist, je nachdem, was kleiner ist." Kann jemand mir eine Idee davon geben, warum ich eine Größe von 8 und nicht 7 bekomme?

+2

Die Dokumentation sagt "... wird auf einer Grenze sein, die ist ..."; Ich kann jedoch nicht finden, wo es etwas über eine Größengarantie sagt. –

Antwort

5

MSVC++ weist immer mindestens eine Speichereinheit zu, die dem Typ entspricht, den Sie für Ihr Bitfeld verwendet haben. Sie haben unsigned int verwendet, was bedeutet, dass zuerst eine unsigned int zugewiesen wird und eine weitere unsigned int zugewiesen wird, wenn die erste erschöpft ist. Es gibt keine Möglichkeit, MSVC++ zu zwingen, den unbenutzten Teil des zweiten unsigned int zu trimmen.

Grundsätzlich interpretiert MSVC++ Ihre unsigned int als eine Möglichkeit, die Ausrichtung Anforderungen für die gesamte Struktur auszudrücken.

Verwenden kleinere Typen für Ihre Bit-Felder (unsigned short und unsigned char) und die Bit-Felder neu zu gruppieren, so dass sie die zugewiesene Einheit vollständig füllen - so sollten Sie möglichst eng die Lage sein, Dinge zu packen.

+0

In diesem Fall kann er nichts speichern, wenn er die 15 und 16 Bits benötigt, er würde lieber mit mindestens 9 Bytes enden, da er bereits 56 Bits verwendet. – Vinzenz

+0

Nein ... Mit diesem Rat kann ich es tatsächlich in 7 Bytes packen (danke AndreyT). Ich habe keine Ahnung, was du mit 9 Bytes meinst. – Rooke

+0

@Rooke: Beachten Sie, dass "unsigned Char Bits: 10" gültig ist, aber wahrscheinlich nicht, was Sie meinen (wird nur bis zu 8 Bit-Werte speichern und 2 extra Bits für Padding reservieren). I.e. Es ist von kritischer Wichtigkeit, die Bitfelder neu zu gruppieren, so dass jedes Bitfeld in die zugewiesene Einheit passt. –

3

Bitfelder werden in dem von Ihnen definierten Typ gespeichert. Da Sie unsigned int verwenden und nicht in eine einzige unsigned int passen, muss der Compiler eine zweite ganze Zahl verwenden und die letzten 24 Bits in dieser letzten Ganzzahl speichern.

+0

nein das ist falsch. Der Compiler kann Bitfelder in jedem beliebigen Typ speichern. – Abyx

+0

@Abyx das geht gegen jede andere Antwort und Kommentar hier. Wenn Sie etwas haben, um Ihre Behauptung zu unterstützen, würden wir es gerne sehen. – Spencer

1

Nun, Sie verwenden unsigned int, die in diesem Fall 32 Bit ist. Die nächste Grenze (um in das Bitfeld zu passen) für unsigned int ist 64 Bit => 8 Bytes.

0

pst ist richtig. Die Mitglieder sind auf 1-Byte-Grenzen ausgerichtet (oder kleiner, da es ein Bitfeld ist). Die Gesamtstruktur hat die Größe 8 und ist an einer 8-Byte-Grenze ausgerichtet. Dies entspricht sowohl dem Standard als auch der pack Option. Die Dokumentation sagt nie, dass am Ende keine Auffüllung stattfinden wird.

+0

Das Problem ist nicht mit Auffüllen am Ende, sondern mit der Tatsache, dass Bitfelder in Einheiten des Bitfeldtyps gepackt sind. Die Struktur hat überhaupt keine Auffüllung, nur zwei "unsigned int" -Mitglieder. –

+0

@David, sagt der Standard (§6.7.2.1), "Ein Bit-Feld wird als vorzeichenbehafteter oder vorzeichenloser Integer-Typ interpretiert, der aus der spezifizierten Anzahl von Bits besteht. [...] Eine Implementierung kann irgendeine adressierbare Speichereinheit zuordnen groß genug, um ein bisschen zu halten - Feld. " Ich denke also nicht, dass "unsigned int" bedeutet, dass es tatsächlich "unsigned int" als Speichereinheit verwenden muss, nur ein vorzeichenloser Typ mit genügend Bits. Bit-Felder dürfen auch Speichereinheitsgrenzen überschreiten. Also denke ich, dass es am Ende Polsterung gibt. –

+0

BTW, welchen Standard suchen Sie? Weder der aktuelle C++ - Standard noch der C++ 0x FCD haben Abschnitt 6.7.2.1. –

0

Um ein weiteres interessantes Beispiel zu geben, was passiert, betrachten Sie den Fall, in dem Sie eine Struktur packen wollen, die eine Typgrenze kreuzt. Z.B.

struct state { 
    unsigned int cost  : 24; 
    unsigned int back  : 21; 
    unsigned int a  : 1; 
    unsigned int b  : 1; 
    unsigned int c  : 1; 
}; 

Diese Struktur kann nicht in 6 Bytes mit MSVC gepackt werden, soweit ich weiß.Jedoch können wir den gewünschten Packungseffekt erhalten, indem wir die ersten zwei Felder aufteilen:

struct state_packed { 
    unsigned short cost_1 : 16; 
    unsigned char cost_2 : 8; 
    unsigned short back_1 : 16; 
    unsigned char back_2 : 5; 
    unsigned char a  : 1; 
    unsigned char b  : 1; 
    unsigned char c  : 1; 
}; 

Dies kann tatsächlich in 6 Bytes gepackt werden. Der Zugriff auf das ursprüngliche Kostenfeld ist jedoch äußerst umständlich und hässlich. Ein Verfahren ist ein state_packed Zeiger auf eine spezielle Dummy-Struktur zu werfen:

struct state_cost { 
    unsigned int cost  : 24; 
    unsigned int junk  : 8; 
}; 

state_packed sc; 
state_packed *p_sc = &sc; 

sc.a = 1; 
(*(struct state_cost *)p_sc).cost = 12345; 
sc.b = 1; 

Wenn jemand eine elegantere Art und Weise, dies zu tun weiß, ich würde gerne wissen!

Verwandte Themen