Dlaczego ta struktura ma rozmiar 3 zamiast 2?

91

Zdefiniowałem tę strukturę:

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

sizeof(col)Dać mi wyjście 3, ale nie powinno to być 2? Jeśli skomentuję tylko jeden element, to sizeofjest 2. Nie rozumiem dlaczego: pięć elementów po 3 bity to 15 bitów, czyli mniej niż 2 bajty.

Czy istnieje „rozmiar wewnętrzny” w definiowaniu struktury takiej jak ta? Potrzebuję tylko wyjaśnienia, ponieważ z mojego dotychczasowego pojęcia o języku spodziewałem się rozmiaru 2 bajtów, a nie 3.

Raffaello
źródło
4
To prawdopodobnie optymalizacja wyrównania. Rozpoczyna nowy bajt, jeśli następny rozmiar bitu nie zmieści się w faktycznie zajmowanej przestrzeni.
πάντα ῥεῖ
4
O ile nie masz zewnętrznych ograniczeń wymagających pakowania bitów, a Twoja platforma zapewnia dodatkowe gwarancje w stosunku do tego, co oferuje standardowa, nie ma sensu używać pól bitowych.
David Rodríguez - dribeas
3
Zwróć uwagę, że w C użycie char jest mniej przenośne niż użycie int, stackoverflow.com/a/23987436/23118 .
hlovdal
2
Zauważ, że prawie wszystko, co dotyczy pól bitowych, jest zdefiniowane jako implementacja. Możesz uzyskać różne odpowiedzi od różnych kompilatorów i nie będzie możliwości odwołania. Zauważ również, że ponieważ nie określiłeś signed charlub unsigned charnie możesz powiedzieć bez spojrzenia na dokumentację, czy kompilator potraktuje słowo „zwykłe” charw polu bitowym jako podpisane czy niepodpisane, a decyzja może (teoretycznie) różnić się od decyzji o tym, czy „Zwykły” charjest podpisany lub niepodpisany, gdy jest używany poza polem bitowym.
Jonathan Leffler
3
W szczególności, w C99, §6.7.2.1 Struct i Specyfikatory związkowi, ¶4 Trochę pola mają typ, który jest wykwalifikowany lub niewykwalifikowany wersja _Bool, signed int, unsigned int, lub jakiegoś innego typu implementacji zdefiniowane. Stosowanie charnależy zatem do kategorii „inny typ zdefiniowany w ramach implementacji”.
Jonathan Leffler

Odpowiedzi:

95

Ponieważ używasz charjako podstawowego typu dla swoich pól, kompilator próbuje grupować bity według bajtów, a ponieważ nie może umieścić więcej niż osiem bitów w każdym bajcie, może przechowywać tylko dwa pola na bajt.

Całkowita suma bitów używanych przez twoją strukturę wynosi 15, więc idealny rozmiar, aby zmieścić tak dużo danych, to 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));

}

Powyższy kod (dla platformy 64-bitowej, takiej jak moja), rzeczywiście da 2drugą strukturę. W przypadku czegokolwiek większego niż a short, struktura wypełni nie więcej niż jeden element użytego typu, więc - dla tej samej platformy - struktura będzie miała rozmiar cztery dla int, osiem dla longitd.

didierc
źródło
1
Proponowana definicja struktury jest nadal błędna. Prawidłowa definicja struktury użyłaby „unsigned short”.
user3629249
21
@ user3629249 Dlaczego krótki bez znaku jest „prawidłowy”? Jeśli użytkownik chce zapisać od -4 do 3, to krótki jest poprawny. Jeśli użytkownik chce zapisać od 0 do 7, to bez znaku jest poprawny. Oryginalne pytanie zawierało podpisany tekst, ale nie mogę powiedzieć, czy było to zamierzone, czy przypadkowe.
Bruce Dawson
2
Dlaczego istnieje różnica między chara short?
GingerPlusPlus
5
@BruceDawson: Standard zezwala na charniepodpisanie implementacji …
Thomas Eding
@ThomasEding Prawda, standard zezwala na brak znaku char. Ale moim głównym punktem pozostaje to, że nie podano powodu, aby twierdzić, że bez znaku był poprawny (chociaż zwykle będzie).
Bruce Dawson
78

Ponieważ nie możesz mieć pola bitowego pakietu, które obejmuje minimalną granicę wyrównania (która wynosi 1 bajt), więc prawdopodobnie zostaną spakowane jak

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

(kolejność pól / dopełnień w tym samym bajcie nie jest zamierzona, ma tylko dać ci pomysł, ponieważ kompilator może je określić tak, jak woli)

Jacek
źródło
16

Pierwsze dwa pola bitów pasują do jednego char. Trzeci nie może do tego pasować chari potrzebuje nowego. 3 + 3 + 3 = 9, który nie pasuje do 8-bitowego znaku.

Zatem pierwsza para przyjmuje a char, druga para przyjmuje a char, a ostatnie pole bitowe otrzymuje trzecie char.

2501
źródło
15

Większość kompilatorów pozwala kontrolować wypełnienie, np. Używając #pragmas . Oto przykład z 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
}

Zauważ, że domyślne zachowanie kompilatora ma swój powód i prawdopodobnie zapewni lepszą wydajność.

Kos
źródło
9

Chociaż norma ANSI C zbyt mało określa, jak upakowane są pola bitów, aby zapewnić jakąkolwiek znaczącą przewagę nad „kompilatorami mogą pakować pola bitowe w dowolny sposób, jak uznają to za stosowne”, to jednak w wielu przypadkach zabrania kompilatorom pakowania rzeczy w najbardziej efektywny sposób.

W szczególności, jeśli struktura zawiera pola bitowe, kompilator musi przechowywać ją jako strukturę, która zawiera jedno lub więcej anonimowych pól pewnego „normalnego” typu pamięci, a następnie logicznie podzielić każde takie pole na części składowe pola bitowego. Zatem biorąc pod uwagę:

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;

Jeśli unsigned charma 8 bitów, kompilator musiałby przydzielić cztery pola tego typu i przypisać dwa pola bitowe do wszystkich oprócz jednego (które znajdowałoby się we charwłasnym polu). Gdyby wszystkie chardeklaracje zostały zastąpione przez short, byłyby dwa pola typu short, z których jedno zawierałoby pięć pól bitowych, a drugie zawierałoby pozostałe dwa.

Na procesorze bez ograniczeń wyrównania dane można by układać wydajniej, używając unsigned shortdla pierwszych pięciu pól i unsigned chardla ostatnich dwóch, przechowując siedem trzybitowych pól w trzech bajtach. Chociaż powinno być możliwe przechowywanie ośmiu trzy-bitowych pól w trzech bajtach, kompilator mógłby zezwolić na to tylko wtedy, gdyby istniał trzy bajtowy typ liczbowy, który mógłby być użyty jako typ „pola zewnętrznego”.

Osobiście uważam, że pola bitowe są zdefiniowane jako zasadniczo bezużyteczne. Jeśli kod musi działać z danymi spakowanymi binarnie, powinien wyraźnie zdefiniować lokalizacje przechowywania rzeczywistych typów, a następnie użyć makr lub innych takich środków, aby uzyskać dostęp do ich bitów. Byłoby pomocne, gdyby C obsługiwał składnię taką jak:

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;

Taka składnia, jeśli jest dozwolona, ​​umożliwiłaby kodowi używanie pól bitowych w sposób przenośny, bez względu na rozmiary słów lub porządek bajtów (foo0 znajdowałby się w trzech najmniej znaczących bitach f1, ale te mogłyby być przechowywane w niższy lub wyższy adres). Jednak bez takiej funkcji makra są prawdopodobnie jedynym przenośnym sposobem obsługi takich rzeczy.

supercat
źródło
2
Różne kompilatory inaczej układają pola bitowe. Napisałem dokumentację dotyczącą tego, jak robi to Visual C ++, która może być istotna. Wskazuje na niektóre z irytujących pułapek: randomascii.wordpress.com/2010/06/06/…
Bruce Dawson
Cóż, mówisz odpowiednik przechowywania w normalnym typie i użyj operatora pola bitowego, aby uzyskać pojedynczą zmienną będącą przedmiotem zainteresowania, i aby uprościć ten mechanizm, użyj makra. Myślę, że wygenerowany kod w c / c ++ robi coś takiego. Używanie struktury służy tylko do „lepszej” organizacji kodu, w rzeczywistości wcale nie jest konieczne.
Raffaello