Szukaj…


Wprowadzenie

Większość zmiennych w C ma rozmiar, który jest całkowitą liczbą bajtów. Pola bitowe są częścią struktury, która niekoniecznie zajmuje całkowitą liczbę bajtów; mogą dowolną liczbę bitów. Wiele pól bitowych może być zapakowanych w jedną jednostkę pamięci. Są częścią standardowego C, ale istnieje wiele aspektów, które są zdefiniowane w implementacji. Są jedną z najmniej przenośnych części C.

Składnia

  • identyfikator specyfikatora typu: rozmiar;

Parametry

Parametr Opis
specyfikator typu signed , unsigned , int lub _Bool
identyfikator Nazwa tego pola w strukturze
rozmiar Liczba bitów używanych w tym polu

Uwagi

Jedyne typy przenośne dla pól bitowych są signed , unsigned lub _Bool . Można użyć zwykłego typu int , ale standard mówi (§6.7.2¶5) … dla pól bitowych, jest określone w implementacji, czy specyfikator int oznacza ten sam typ jako signed int czy ten sam typ jako unsigned int .

Inne typy liczb całkowitych mogą być dozwolone przez określoną implementację, ale ich użycie nie jest przenośne.

Pola bitowe

Proste pole bitowe może być użyte do opisania rzeczy, które mogą mieć określoną liczbę bitów.

struct encoderPosition {
   unsigned int encoderCounts : 23;
   unsigned int encoderTurns  : 4;
   unsigned int _reserved     : 5;
};

W tym przykładzie rozważamy enkoder z 23 bitami pojedynczej precyzji i 4 bitami do opisania wieloobrotowego. Pola bitowe są często używane podczas łączenia ze sprzętem, który generuje dane związane z określoną liczbą bitów. Innym przykładem może być komunikacja z układem FPGA, w którym układ FPGA zapisuje dane w pamięci w 32-bitowych sekcjach, umożliwiając odczyt sprzętu:

struct FPGAInfo {
    union {
        struct bits {
            unsigned int bulb1On  : 1;
            unsigned int bulb2On  : 1;
            unsigned int bulb1Off : 1;
            unsigned int bulb2Off : 1;
            unsigned int jetOn    : 1;
        };
        unsigned int data;
   };
};

W tym przykładzie pokazaliśmy powszechnie używaną konstrukcję, która umożliwia dostęp do danych w poszczególnych bitach lub zapisanie pakietu danych jako całości (emulowanie tego, co może zrobić FPGA). Możemy wtedy uzyskać dostęp do takich bitów:

FPGAInfo fInfo;
fInfo.data = 0xFF34F;
if (fInfo.bits.bulb1On) {
    printf("Bulb 1 is on\n");
}

Jest to ważne, ale zgodnie z normą C99 6.7.2.1, pozycja 10:

Kolejność alokacji pól bitowych w jednostce (od wysokiego do niskiego lub od niskiego do wysokiego) jest zdefiniowana w implementacji.

Musisz być świadomy endianizmu podczas definiowania pól bitowych w ten sposób. Jako takie może być konieczne użycie dyrektywy preprocesora, aby sprawdzić endianność maszyny. Oto przykład:

typedef union {
    struct bits {
#if defined(WIN32) || defined(LITTLE_ENDIAN)
    uint8_t commFailure :1;
    uint8_t hardwareFailure :1;
    uint8_t _reserved :6;
#else
    uint8_t _reserved :6;
    uint8_t hardwareFailure :1;
    uint8_t commFailure :1;
#endif
    };
    uint8_t data;
} hardwareStatus;

Używanie pól bitowych jako małych liczb całkowitych

#include <stdio.h>

int main(void)
{
    /* define a small bit-field that can hold values from 0 .. 7 */
    struct
    {
        unsigned int uint3: 3;
    } small;

    /* extract the right 3 bits from a value */
    unsigned int value = 255 - 2; /* Binary 11111101 */
    small.uint3 = value;          /* Binary      101 */
    printf("%d", small.uint3);

    /* This is in effect an infinite loop */
    for (small.uint3 = 0; small.uint3 < 8; small.uint3++)
    {
        printf("%d\n", small.uint3);
    }

    return 0;
}

Wyrównanie pola bitowego

Pola bitowe dają możliwość deklarowania pól struktur, które są mniejsze niż szerokość znaków. Pola bitowe są implementowane za pomocą maski na poziomie bajtów lub słów. Poniższy przykład daje strukturę 8 bajtów.

struct C
{
    short s;            /* 2 bytes */
    char  c;            /* 1 byte */
    int   bit1 : 1;     /* 1 bit */
    int   nib  : 4;     /* 4 bits padded up to boundary of 8 bits. Thus 3 bits are padded */
    int   sept : 7;     /* 7 Bits septet, padded up to boundary of 32 bits. */
};

Komentarze opisują jeden możliwy układ, ale ponieważ standard mówi, że wyrównanie adresowalnej jednostki pamięci jest nieokreślone , możliwe są również inne układy.

Nienazwane pole bitowe może mieć dowolny rozmiar, ale nie można ich inicjować ani odwoływać.

Pole bitowe o zerowej szerokości nie może otrzymać nazwy i wyrównuje następne pole do granicy określonej przez typ danych pola bitowego. Osiąga się to poprzez wypełnianie bitów między polami bitowymi.

Rozmiar struktury „A” wynosi 1 bajt.

struct A
{
    unsigned char c1 : 3;
    unsigned char c2 : 4;
    unsigned char c3 : 1;
};

W strukturze B pierwsze nienazwane pole bitowe pomija 2 bity; pole bitowe o zerowej szerokości po c2 powoduje, że c3 zaczyna się od granicy char (więc 3 bity są pomijane między c2 i c3 . Istnieją 3 bity wypełniające po c4 . Zatem wielkość struktury wynosi 2 bajty.

struct B
{
    unsigned char c1 : 1;
    unsigned char    : 2;    /* Skips 2 bits in the layout */
    unsigned char c2 : 2;
    unsigned char    : 0;    /* Causes padding up to next container boundary */ 
    unsigned char c3 : 4;
    unsigned char c4 : 1;
};

Kiedy pola bitowe są przydatne?

Pole bitowe służy do łączenia wielu zmiennych w jeden obiekt, podobny do struktury. Pozwala to zmniejszyć zużycie pamięci i jest szczególnie przydatne w środowisku osadzonym.

 e.g. consider the following variables having the ranges as given below.
 a --> range 0 - 3
 b --> range 0 - 1
 c --> range 0 - 7
 d --> range 0 - 1
 e --> range 0 - 1

Jeśli zadeklarujemy te zmienne osobno, każda z nich musi być co najmniej 8-bitową liczbą całkowitą, a całkowita wymagana przestrzeń będzie wynosić 5 bajtów. Ponadto zmienne nie będą wykorzystywać całego zakresu 8-bitowej liczby całkowitej bez znaku (0–255). Tutaj możemy użyć pól bitowych.

typedef struct {
   unsigned int a:2;
   unsigned int b:1;
   unsigned int c:3;
   unsigned int d:1;
   unsigned int e:1;
} bit_a;

Dostęp do pól bitowych w strukturze jest taki sam, jak w każdej innej strukturze. Programista musi zadbać o to, aby zmienne były zapisane w zakresie. Jeśli jest poza zasięgiem, zachowanie jest niezdefiniowane.

int main(void)
{
   bit_a bita_var;
   bita_var.a = 2;              // to write into element a
   printf ("%d",bita_var.a);    // to read from element a.
   return 0;
}

Często programista chce wyzerować zestaw pól bitowych. Można to zrobić element po elemencie, ale istnieje druga metoda. Wystarczy utworzyć połączenie powyższej struktury z typem bez znaku, który jest większy lub równy rozmiarowi struktury. Następnie cały zestaw pól bitowych może zostać wyzerowany przez wyzerowanie tej liczby całkowitej bez znaku.

typedef union {
    struct {
       unsigned int a:2;
       unsigned int b:1;
       unsigned int c:3;
       unsigned int d:1;
       unsigned int e:1;
    };
    uint8_t data;
} union_bit;    

Użycie jest następujące

int main(void)
{
   union_bit un_bit;
   un_bit.data = 0x00;        // clear the whole bit-field
   un_bit.a = 2;              // write into element a
   printf ("%d",un_bit.a);    // read from element a.
   return 0;
}

Podsumowując, pola bitowe są powszechnie używane w sytuacjach ograniczonych pamięci, w których istnieje wiele zmiennych, które mogą przyjmować ograniczone zakresy.

Nie dla pól bitowych

  1. Tablice pól bitowych, wskaźniki do pól bitowych i funkcje zwracające pola bitowe są niedozwolone.
  2. Operatora adresu (&) nie można zastosować do elementów pola bitowego.
  3. Typ danych pola bitowego musi być wystarczająco szeroki, aby zawierał rozmiar pola.
  4. Operatora sizeof() nie można zastosować do pola bitowego.
  5. Nie ma sposobu, aby utworzyć typedef dla pola bitowego w oderwaniu (choć z pewnością można utworzyć typedef dla struktury zawierającej pola bitowe).
typedef struct mybitfield
{
    unsigned char c1 : 20;   /* incorrect, see point 3 */
    unsigned char c2 : 4;    /* correct */
    unsigned char c3 : 1;
    unsigned int x[10]: 5;   /* incorrect, see point 1 */
} A;

int SomeFunction(void)
{
    // Somewhere in the code
    A a = { … };
    printf("Address of a.c2 is %p\n", &a.c2);      /* incorrect, see point 2 */
    printf("Size of a.c2 is %zu\n", sizeof(a.c2)); /* incorrect, see point 4 */
}


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow