Warum ist diese Struktur Größe 3 statt 2?

91

Ich habe diese Struktur definiert:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

Das sizeof(col)gibt mir die Ausgabe von 3, aber sollte es nicht 2 sein? Wenn ich nur ein Element kommentiere, sizeofist das 2. Ich verstehe nicht warum: Fünf Elemente mit 3 Bits entsprechen 15 Bits, und das sind weniger als 2 Bytes.

Gibt es eine "interne Größe" bei der Definition einer Struktur wie dieser? Ich brauche nur eine Klarstellung, weil ich von meiner bisherigen Vorstellung von der Sprache eine Größe von 2 Byte erwartet habe, nicht von 3.

Raffaello
quelle
4
Es ist wahrscheinlich eine Optimierung der Ausrichtung. Es wird ein neues Byte gestartet, wenn die nächste Bitgröße nicht in den tatsächlich belegten Speicherplatz passt.
πάντα ῥεῖ
4
Sofern Sie keine externen Einschränkungen haben, die das Packen von Bits erfordern, und Ihre Plattform einige zusätzliche Garantien gegenüber den Standardangeboten bietet, ist die Verwendung von Bitfeldern wenig sinnvoll.
David Rodríguez - Dribeas
3
Beachten Sie, dass die Verwendung von char für C weniger portabel ist als die Verwendung von int, stackoverflow.com/a/23987436/23118 .
Hlovdal
2
Beachten Sie, dass fast alles an Bitfeldern implementiert ist. Möglicherweise erhalten Sie unterschiedliche Antworten von verschiedenen Compilern, und es gibt keinen Rückgriff. Beachten Sie auch, dass Sie, weil Sie nicht angegeben haben signed charoder unsigned charnicht, ohne in der Dokumentation zu sehen, ob der Compiler 'plain' charin einem Bitfeld als signiert oder nicht signiert behandelt, nicht sagen können und die Entscheidung (theoretisch) von der Entscheidung darüber abweichen kann, ob 'plain' charist signiert oder nicht signiert, wenn es außerhalb eines Bitfelds verwendet wird.
Jonathan Leffler
3
Insbesondere in C99, §6.7.2.1 Struct und Vereinigung Bezeich ¶4 Ein Bitfeld ist eine Art, die eine qualifizierte oder unqualifizierte Version des Typs ist _Bool, signed int, unsigned intoder eine andere Implementierung definierte Art. Die Verwendung charfällt daher in die Kategorie "anderer implementierungsdefinierter Typ".
Jonathan Leffler

Antworten:

95

Da Sie charden zugrunde liegenden Typ für Ihre Felder verwenden, versucht der Compiler, Bits nach Bytes zu gruppieren. Da nicht mehr als acht Bits in jedes Byte eingefügt werden können, können nur zwei Felder pro Byte gespeichert werden.

Die Gesamtsumme der von Ihrer Struktur verwendeten Bits beträgt 15, sodass die ideale Größe für so viele Daten a wäre short.

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

Der obige Code (für eine 64-Bit-Plattform wie meine) ergibt tatsächlich 2die zweite Struktur. Für alles, was größer als a ist short, füllt die Struktur nicht mehr als ein Element des verwendeten Typs, sodass die Struktur für dieselbe Plattform die Größe vier für int, acht für longusw. hat.

didierc
quelle
1
Die vorgeschlagene Strukturdefinition ist immer noch falsch. Die korrekte Strukturdefinition würde 'unsigned short' verwenden.
user3629249
21
@ user3629249 Warum ist unsigned short 'korrekt'? Wenn der Benutzer von -4 bis 3 speichern möchte, ist kurz richtig. Wenn der Benutzer von 0 bis 7 speichern möchte, ist ein nicht signierter Kurzschluss korrekt. Die ursprüngliche Frage verwendete einen signierten Typ, aber ich kann nicht sagen, ob dies beabsichtigt oder zufällig war.
Bruce Dawson
2
Warum gibt es den Unterschied zwischen charund short?
GingerPlusPlus
5
@ BruceDawson: Der Standard erlaubt, dass Implementierungen charnicht signiert sind…
Thomas Eding
@ThomasEding True, der Standard erlaubt es, Zeichen ohne Vorzeichen zu lassen. Mein Hauptpunkt bleibt jedoch, dass kein Grund angegeben wurde, zu behaupten, dass ein nicht signierter Kurzfilm korrekt war (obwohl dies normalerweise der Fall sein wird).
Bruce Dawson
78

Da Sie kein Bitpaketfeld haben können, das sich über die minimale Ausrichtungsgrenze (die 1 Byte beträgt) erstreckt, werden sie wahrscheinlich wie gepackt

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(Die Reihenfolge der Felder / Auffüllungen innerhalb desselben Bytes ist nicht beabsichtigt, sondern soll Ihnen nur die Idee geben, da der Compiler sie so festlegen kann, wie er es bevorzugt.)

Jack
quelle
16

Die ersten beiden Bitfelder passen in ein einzelnes char. Der dritte kann nicht dazu passen charund braucht einen neuen. 3 + 3 + 3 = 9, was nicht in ein 8-Bit-Zeichen passt.

Das erste Paar nimmt also a char, das zweite Paar a charund das letzte Bitfeld ein drittes char.

2501
quelle
15

Bei den meisten Compilern können Sie das Auffüllen steuern, z#pragma . B. mit s . Hier ist ein Beispiel mit GCC 4.8.1:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

Beachten Sie, dass das Standardverhalten des Compilers aus einem bestimmten Grund vorliegt und wahrscheinlich zu einer besseren Leistung führt.

Kos
quelle
9

Obwohl der ANSI C-Standard zu wenig darüber spezifiziert, wie Bitfelder gepackt werden, um einen signifikanten Vorteil gegenüber "Compilern, die Bitfelder packen dürfen, wie sie es für richtig halten" zu bieten, verbietet er Compilern in vielen Fällen, Dinge auf die effizienteste Weise zu packen.

Insbesondere wenn eine Struktur Bitfelder enthält, muss ein Compiler sie als Struktur speichern, die ein oder mehrere anonyme Felder eines "normalen" Speichertyps enthält, und dann jedes dieser Felder logisch in seine konstituierenden Bitfeldteile unterteilen. Also gegeben:

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

Wenn unsigned char8 Bit vorhanden sind, muss der Compiler vier Felder dieses Typs zuweisen und allen bis auf eines zwei Bitfelder zuweisen (die sich in einem eigenen charFeld befinden würden ). Wenn alle charDeklarationen durch ersetzt shortworden wären, gäbe es zwei Typfeldershort Typfelder, von denen eines fünf Bitfelder und das andere die verbleibenden zwei würde.

Auf einem Prozessor ohne Ausrichtungsbeschränkungen könnten die Daten effizienter ausgelegt werden, indem unsigned shortfür die ersten fünf Felder und verwendet werdenunsigned char für die letzten zwei Felder sieben Drei-Bit-Felder in drei Bytes gespeichert werden. Während es möglich sein sollte, acht Drei-Bit-Felder in drei Bytes zu speichern, konnte ein Compiler dies nur zulassen, wenn ein numerischer Drei-Byte-Typ vorhanden war, der als Typ "äußeres Feld" verwendet werden konnte.

Persönlich halte ich Bitfelder als definiert für grundsätzlich nutzlos. Wenn Code mit binär gepackten Daten arbeiten muss, sollte er explizit Speicherorte tatsächlicher Typen definieren und dann Makros oder andere solche Mittel verwenden, um auf deren Bits zuzugreifen. Es wäre hilfreich, wenn C eine Syntax wie die folgende unterstützen würde:

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

Eine solche Syntax würde es dem Code ermöglichen, Bitfelder auf tragbare Weise zu verwenden, ohne Rücksicht auf Wortgrößen oder Bytereihenfolgen (foo0 würde in den drei niedrigstwertigen Bits von f1 liegen, aber diese könnten im gespeichert werden niedrigere oder höhere Adresse). Ohne eine solche Funktion sind Makros jedoch wahrscheinlich die einzige tragbare Möglichkeit, mit solchen Dingen zu arbeiten.

Superkatze
quelle
2
Verschiedene Compiler legen Bitfelder unterschiedlich an. Ich habe eine Dokumentation darüber geschrieben, wie Visual C ++ dies tut, die möglicherweise relevant ist. Es weist auf einige der ärgerlichen Fallstricke hin: randomascii.wordpress.com/2010/06/06/…
Bruce Dawson
Nun, Sie sagen ein Äquivalent zum Speichern in einem normalen Typ und verwenden den Bitfeldoperator, um die einzelne interessierende Variable zu erreichen und diesen Mechanismus zu vereinfachen. Verwenden Sie ein Makro. Ich denke, der generierte Code in c / c ++ macht auch so etwas. Die Verwendung einer Struktur ist nur für eine "bessere" Organisation des Codes gedacht, in der Tat überhaupt nicht notwendig.
Raffaello