wake-up-neo.com

Ungenaue Größe einer Struktur mit Bitfeldern in meinem Buch

Ich studiere die Grundlagen der C-Sprache. Ich bin im Kapitel der Strukturen mit Bitfeldern angekommen. Das Buch zeigt ein Beispiel für eine Struktur mit zwei verschiedenen Datentypen: verschiedene Bools und verschiedene unsignierte Ints.

Das Buch erklärt, dass die Struktur eine Größe von 16 Bit hat und dass die Struktur ohne Verwendung der Auffüllung 10 Bit messen würde.

Dies ist die Struktur, die das Buch im Beispiel verwendet:

#include <stdio.h>
#include <stdbool.h>

struct test{

       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};

int main(void)
{
       struct test Test;

       printf("%zu\n", sizeof(Test));

       return 0;
}

Warum misst mein Compiler stattdessen exakt 16 Bytes (anstatt Bits) mit Padding und 16 Bytes ohne Padding?

Ich benutze 

GCC (tdm-1) 4.9.2 compiler;
Code::Blocks as IDE.
Windows 7 64 Bit
Intel CPU 64 bit

Das ist das Ergebnis, das ich bekomme:

 enter image description here

Hier ist ein Bild von der Seite, auf der sich das Beispiel befindet:

 enter image description here

23
AlexQualcosa

Die Microsoft-ABI legt Bitfelder auf eine andere Art und Weise fest als GCC auf anderen Plattformen. Sie können das Microsoft-kompatible Layout mit der Option -mms-bitfields verwenden oder es mit -mno-ms-bitfields deaktivieren. Wahrscheinlich verwendet Ihre GCC-Version standardmäßig -mms-bitfields.

Laut Dokumentation ist When -mms-bitfields aktiviert:

  • Jedes Datenobjekt hat eine Ausrichtungsanforderung. Die Ausrichtungsanforderung für alle Daten außer Strukturen, Vereinigungen und Arrays ist entweder die Größe des Objekts oder die aktuelle Packungsgröße (angegeben entweder mit dem ausgerichteten Attribut oder dem Pack-Pragma), je nachdem, welcher Wert niedriger ist. Für Strukturen, Vereinigungen und Arrays ist die Ausrichtungsanforderung die größte Ausrichtungsanforderung seiner Mitglieder. Jedem Objekt wird ein Offset zugewiesen, so dass: Offset% Ausrichtungsanforderung == 0
  • Benachbarte Bitfelder werden in die gleiche 1-, 2- oder 4-Byte-Zuordnungseinheit gepackt, wenn die Integraltypen die gleiche Größe haben und das nächste Bitfeld in die aktuelle Zuordnungseinheit passt, ohne die durch die gemeinsame Grenze auferlegte Grenze zu überschreiten Ausrichtungsanforderungen der Bitfelder.

Da bool und unsigned int unterschiedliche Größen haben, werden sie separat gepackt und ausgerichtet, was die Strukturgröße erheblich erhöht. Die Ausrichtung von unsigned int beträgt 4 Bytes, und die dreifache Neuausrichtung in der Mitte der Struktur führt zu einer Gesamtgröße von 16 Bytes.

Sie können das gleiche Verhalten des Buchs erhalten, indem Sie bool in unsigned int ändern oder -mno-ms-bitfields angeben (dies bedeutet jedoch, dass Sie nicht mit Code arbeiten können, der auf Microsoft-Compilern kompiliert wurde).

Beachten Sie, dass der C-Standard nicht angibt, wie Bitfelder angeordnet sind. Was Ihr Buch sagt, kann für einige Plattformen zutreffen, aber nicht für alle.

28
interjay

In Ihrem Text werden die Bestimmungen des C-Sprachstandards beschrieben und erheben ungerechtfertigte Ansprüche. Konkret sagt der Standard not nicht nur aus, dass unsigned int die grundlegende Layouteinheit von Strukturen jeglicher Art ist, sondern lehnt ausdrücklich jegliche Anforderung an die Größe der Speichereinheiten ab, in denen Bitfelddarstellungen gespeichert sind:

Eine Implementierung kann eine beliebige adressierbare Speichereinheit groß zuweisen. genug, um ein Bitfeld zu halten.

( C2011, 6.7.2.1/11 )

Der Text macht auch Annahmen über das Auffüllen, die vom Standard nicht unterstützt werden. Eine C-Implementierung kann beliebig viele Auffüllungen nach einem oder allen Elementen und Bitfeld-Speichereinheiten einer struct enthalten, einschließlich der letzten. Implementierungen verwenden normalerweise diese Freiheit, um Überlegungen zur Datenausrichtung zu berücksichtigen, aber C beschränkt die Auffüllung nicht auf diese Verwendung. Dies ist völlig unabhängig von den unbenannten Bitfeldern, auf die sich Ihr Text als "Auffüllen" bezieht.

Ich schätze, das Buch sollte jedoch empfohlen werden, um zu vermeiden, dass der deklarierte Datentyp eines Bitfelds mit der Größe der Speichereinheit (en), in der sich ihre Darstellung befindet, zu tun hat. Der Standard macht keine solche Zuordnung.

Warum misst mein Compiler stattdessen genau 16 Bytes (anstatt Bits) mit Padding und 16 Bytes ohne Padding?

Um den Text so weit wie möglich zu reduzieren, unterscheidet er zwischen der Anzahl der von Mitgliedern besetzten Datenbits (insgesamt 16 Bits, sechs unbenannten Bitfeldern) und der Gesamtgröße der Instanzen von struct. Es scheint zu behaupten, dass die Gesamtstruktur die Größe eines unsigned int hat, der auf dem System, das er beschreibt, anscheinend 32 Bit aufweist, und das wäre für beide Versionen der Struktur gleich.

Grundsätzlich könnten Ihre beobachteten Größen durch Ihre Implementierung mit einem 128-Bit-Speicher für die Bitfelder erklärt werden. In der Praxis werden wahrscheinlich eine oder mehrere kleinere Speichereinheiten verwendet, so dass ein Teil des zusätzlichen Platzes in jeder Struktur auf die durch Implementierung bereitgestellte Auffüllung zurückzuführen ist, wie ich sie oben anführe.

Es ist üblich, dass C-Implementierungen für alle Strukturtypen eine Mindestgröße vorgeben und daher Repräsentationen bei Bedarf auf diese Größe auffüllen. Häufig entspricht diese Größe der strengsten Ausrichtungsanforderung eines beliebigen vom System unterstützten Datentyps, obwohl dies wiederum eine Überlegung der Implementierung ist, keine Anforderung der Sprache.

Fazit: Nur wenn Sie sich auf Implementierungsdetails und/oder Erweiterungen verlassen, können Sie die genaue Größe eines struct vorhersagen, unabhängig davon, ob Bitfield-Mitglieder vorhanden oder nicht vorhanden sind.

8
John Bollinger

Zu meiner Überraschung scheint es einen Unterschied zwischen einigen Online-Compilern von GCC 4.9.2 zu geben. Zunächst ist dies mein Code:

#include <stdio.h>
#include <stdbool.h>

struct test {
       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};

struct test_packed {
       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
} __attribute__((packed));

int main(void)
{
       struct test padding;
       struct test_packed no_padding;

       printf("with padding: %zu bytes = %zu bits\n", sizeof(padding), sizeof(padding) * 8);
       printf("without padding: %zu bytes = %zu bits\n", sizeof(no_padding), sizeof(no_padding) * 8);

       return 0;
}

Und jetzt Ergebnisse von verschiedenen Compilern.

GCC 4.9.2 von WandBox:

with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits

GCC 4.9.2 von http://cpp.sh/ :

with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits

ABER

GCC 4.9.2 von theonlinecompiler.com:

with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits

(Um richtig kompilieren zu können, müssen Sie %zu zu %u wechseln)

EDIT

@ interjays answer könnte das erklären. Als ich -mms-bitfields zu GCC 4.9.2 von WandBox hinzufügte, bekam ich folgendes:

with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits

Der C-Standard beschreibt nicht alle Details, wie Variablen im Speicher abgelegt werden sollen. Dies lässt Raum für die Optimierung, die von der verwendeten Plattform abhängt.

Um sich selbst eine Vorstellung davon zu geben, wie sich Dinge im Gedächtnis befinden, können Sie dies folgendermaßen tun:

#include <stdio.h>
#include <stdbool.h>

struct test{

       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};


int main(void)
{
  struct test Test = {0};
  int i;
  printf("%zu\n", sizeof(Test));

  unsigned char* p;
  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  Test.opaque = true;

  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  Test.fill_color = 3;

  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  return 0;
}

Wenn ich dies auf ideone ( https://ideone.com/wbR5tI ) laufe, bekomme ich:

4
00000000
01000000
07000000

Ich kann also sehen, dass sich opaque und fill_color beide im ersten Byte befinden.

16
00000000000000000000000000000000
01000000000000000000000000000000
01000000030000000000000000000000

Hier kann ich also sehen, dass opaque und fill_colornot beide im ersten Byte sind. Es scheint, dass opaque 4 Bytes beansprucht.

Dies bedeutet, dass Sie insgesamt 16 Bytes erhalten, d. H. Die Variable bool benötigt jeweils 4 Bytes und dann 4 Bytes für die Felder dazwischen und danach.

6
4386427

Bevor der Autor die Struktur definiert, von der er sagt, dass er die Bitfelder in zwei Bytes unterteilen möchte, enthält ein Byte die Bitfelder für die füllungsbezogenen Informationen und ein Byte für die grenzbezogenen Informationen.

Um dies zu erreichen, fügt er einige unbenutzte Bits (Bitfeld) hinzu:

unsigned int       4;  // padding of the first byte

er paddelt auch das zweite Byte, aber das ist nicht nötig.

Vor dem Auffüllen würden also 10 Bits verwendet, und nach dem Auffüllen sind 16 Bits definiert (jedoch nicht alle verwendet) .


Hinweis: Der Autor verwendet bool, um ein 1/0-Feld anzugeben. Als nächstes geht der Autor davon aus, dass der _Bool C99-Typ einen Alias ​​auf bool hat. Aber es scheint, als würden Compiler hier etwas verwirrt. Das Ersetzen von bool durch unsigned int würde das Problem lösen. Ab C99:

6.3.2: Das Folgende kann in einem Ausdruck verwendet werden, wo auch immer ein int oder ein vorzeichenloser int sein kann verwendet werden:

  • Ein Bitfeld vom Typ _Bool, int, signed int oder unsigned int.
5
Paul Ogilvie

Sie interpretieren völlig falsch, was das Buch sagt. 

Es sind 16 Bit-Bitfelder deklariert. 6 Bits sind unbenannte Felder, die für nichts verwendet werden können - das ist das Auffüllen. 16 Bits minus 6 Bits entsprechen 10 Bits. Ohne die Auffüllfelder zu zählen, hat die Struktur 10 nützliche Bits. 

Wie viele Bytes die Struktur hat, hängt von der Qualität des Compilers ab. Anscheinend haben Sie einen Compiler gefunden, der keine Bool-Bitfelder in eine Struktur packt, und er verwendet 4 Byte für einen Bool, etwas Speicher für Bitfelder sowie Strukturauffüllung, insgesamt 4 Bytes, weitere 4 Byte für einen Bool, mehr Speicher für Bitfelder , plus Stapelpadding, insgesamt 4 Bytes, also 16 Bytes. Es ist wirklich sehr traurig. Diese Struktur kann durchaus aus zwei Bytes bestehen. 

2
gnasher729

In der Vergangenheit gab es zwei übliche Arten, die Arten von Bitfield-Elementen zu interpretieren:

  1. Prüfen Sie, ob der Typ signiert oder nicht signiert ist, aber ignorieren Sie die Unterscheidung .__ zwischen "char", "short", "int" usw., um zu entscheiden, wo ein Element platziert werden soll

  2. Wenn nicht vor einem Bitfeld ein anderes mit demselben Typ oder dem Entsprechenden vorzeichenbehafteten/unsignierten Typ vorangestellt ist, ordnen Sie ein Objekt dieses Typs zu und platzieren Sie das Bitfeld darin. Platzieren Sie folgende Bitfields mit demselben Typ in diesem Objekt, wenn sie passen.

Ich denke, die Motivation hinter # 2 war, dass auf einer Plattform, auf der 16-Bit-Werte Word-ausgerichtet sein müssen, ein Compiler etwas gegeben hat:

struct foo {
  char x;  // Not a bitfield
  someType f1 : 1;
  someType f2 : 7;
};

ist möglicherweise in der Lage, eine Zwei-Byte-Struktur zuzuordnen, wobei beide Felder im zweiten Byte platziert werden, aber wenn die Struktur gewesen wäre:

struct foo {
  char x;  // Not a bitfield
  someType f1 : 1;
  someType f2 : 15;
};

es wäre notwendig, dass alle von f2 in ein einzelnes 16-Bit-Word passen, was ein Auffüllbyte vor f1 erforderlich machen würde. Aufgrund der Common Initial Sequence-Regel muss f1 in diesen beiden Strukturen identisch angeordnet sein. Dies würde bedeuten, dass, wenn f1 die Common Initial Sequence-Regel erfüllen könnte, selbst in der ersten Struktur eine Auffüllung erforderlich wäre.

Code, der im ersten Fall das dichtere Layout zulassen möchte, kann sagen:

struct foo {
  char x;  // Not a bitfield
  unsigned char f1 : 1;
  unsigned char f2 : 7;
};

und fordern Sie den Compiler auf, beide Bitfelder unmittelbar nach x in ein Byte zu setzen. Da der Typ als unsigned char angegeben ist, muss sich der Compiler nicht um die Möglichkeit eines 15-Bit-Felds kümmern. Wenn das Layout wäre:

struct foo {
  char x;  // Not a bitfield
  unsigned short f1 : 1;
  unsigned short f2 : 7;
};

und die Absicht war, dass f1 und f2 im selben Speicherplatz liegen würden, dann müsste der Compiler f1 so platzieren, dass ein wortorientierter Zugriff für sein "Bunkmate" f2 möglich ist. Wenn der Code wäre:

struct foo {
  char x;  // Not a bitfield
  unsigned char f1 : 1;
  unsigned short f2 : 15;
};

dann würde f1 nach x und f2 in einem Word von selbst platziert werden.

Beachten Sie, dass der C89-Standard eine Syntax hinzugefügt hat, um das Layout zu erzwingen, das verhindert, dass f1 vor dem verwendeten Speicher in ein Byte gestellt wird. f2:

struct foo {
  char x;  // Not a bitfield
  unsigned short : 0;  // Forces next bitfield to start at a "short" boundary
  unsigned short f1 : 1;
  unsigned short f2 : 15;
};

Durch die Hinzufügung der Syntax: 0 in C89 muss der Compiler nicht mehr dazu gezwungen werden, die Änderung von Typen als erzwungenes Alignment zu betrachten, außer bei der Verarbeitung von altem Code.

1
supercat