Manipulacja bitfieldem w C

Klasyczny problem testowania i ustawiania pojedynczych bitów w liczbie całkowitej w C jest prawdopodobnie jedną z najczęstszych umiejętności programowania na średnim poziomie. Ustawiasz i testujesz za pomocą prostych bitmasków, takich jak

unsigned int mask = 1<<11;

if (value & mask) {....} // Test for the bit
value |= mask;    // set the bit
value &= ~mask;   // clear the bit

An ciekawy post na blogu twierdzi, że jest to podatne na błędy, trudne do utrzymania i słaba praktyka. Sam język C zapewnia dostęp na poziomie bitowym, który jest typesafe i portable:

typedef unsigned int boolean_t;
#define FALSE 0
#define TRUE !FALSE
typedef union {
        struct {
                boolean_t user:1;
                boolean_t zero:1;
                boolean_t force:1;
                int :28;                /* unused */
                boolean_t compat:1;     /* bit 31 */
        };
        int raw;
} flags_t;

int
create_object(flags_t flags)
{
        boolean_t is_compat = flags.compat;

        if (is_compat)
                flags.force = FALSE;

        if (flags.force) {
                [...]
        }
        [...]
}

Ale to sprawia, że ja marudzę .

Ciekawe kłótnia, którą miałem z moim współpracownikiem na ten temat, wciąż pozostaje nierozwiązana. Oba style działają i utrzymuję, że klasyczna metoda bitmask jest łatwa, bezpieczna i przejrzysta. Mój współpracownik zgadza się, że jest to powszechne i łatwe, ale metoda bitfield union jest warta dodatkowych kilku linii, aby była przenośna i bezpieczniejsza.

Czy są jeszcze jakieś argumenty dla którejś ze stron? W szczególności czy istnieje jakaś możliwa awaria, być może z endianess, że metoda bitmask może przegapić, ale gdzie metoda struktury jest Bezpieczna?

Author: Valentin Lorentz, 2009-06-25

17 answers

Pola bitowe nie są tak przenośne, jak myślisz, ponieważ "C nie daje gwarancji uporządkowania pól w słowach maszynowych" (Księga C)

Ignorując to, poprawnie użyta , każda z metod jest Bezpieczna. Obie metody umożliwiają również symboliczny dostęp do zmiennych całkowych. Można argumentować, że metoda bitfielda jest łatwiejsza do napisania, ale oznacza również więcej kodu do przejrzenia.

 38
Author: Matthew Flaschen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-25 15:48:17

Jeśli problem polega na tym, że ustawianie i czyszczenie bitów jest podatne na błędy, to właściwą rzeczą do zrobienia jest napisanie funkcji lub makr, aby upewnić się, że robisz to dobrze.

// off the top of my head
#define SET_BIT(val, bitIndex) val |= (1 << bitIndex)
#define CLEAR_BIT(val, bitIndex) val &= ~(1 << bitIndex)
#define TOGGLE_BIT(val, bitIndex) val ^= (1 << bitIndex)
#define BIT_IS_SET(val, bitIndex) (val & (1 << bitIndex)) 

Co sprawia, że Twój kod jest czytelny, jeśli nie masz nic przeciwko temu, że val musi być lvalue z wyjątkiem BIT_IS_SET. Jeśli to cię nie uszczęśliwi, to wyciągasz przypisanie, nawiasujesz je i używasz jako val = SET_BIT( val, someIndex); co będzie równoważne.

Naprawdę, odpowiedzią jest rozważenie oddzielenia tego, co Ty chcesz od tego, jak chcesz to zrobić.

 28
Author: plinth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-25 17:35:17

Pola bitowe są świetne i łatwe do odczytania, ale niestety Język C nie określa układu pól bitowych w pamięci, co oznacza, że są zasadniczo bezużyteczne do radzenia sobie z spakowanymi danymi w formatach dyskowych lub binarnych protokołach przewodowych. Moim zdaniem ta decyzja była błędem projektowym w C - Ritchie mógł wybrać zamówienie i trzymać się go.

 22
Author: Norman Ramsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-27 21:29:27

Musisz myśleć o tym z perspektywy pisarza-poznaj swoją publiczność. Więc jest kilka "odbiorców" do rozważenia.

Najpierw jest klasyczny Programista C, który bitmaskował całe swoje życie i mógł to robić we śnie.

Po drugie jest newb, który nie ma pojęcia, co to wszystko|, & rzeczy jest. Ostatnio programowali php, a teraz pracują dla Ciebie. (Mówię to jako newb, który robi php)

Jeśli napiszesz, aby zaspokoić pierwsze publiczność (czyli maska bitowa-przez cały dzień), sprawisz, że będą bardzo szczęśliwi, a oni będą w stanie utrzymać kod z zawiązanymi oczami. Jednak newb prawdopodobnie będzie musiał pokonać dużą krzywą uczenia się, zanim będzie w stanie utrzymać Twój kod. Będą musieli dowiedzieć się o operatorach binarnych, jak używasz tych operacji do ustawiania/usuwania bitów itp. Prawie na pewno będziesz miał błędy wprowadzone przez newb, ponieważ on / ona wszystkie sztuczki wymagane, aby to zadziałało.

Z drugiej strony, jeśli napisz, aby zadowolić drugą publiczność, newbs będzie miał łatwiejszy czas utrzymywania kodu. Będą mieli łatwiejszy czas na granie

 flags.force = 0;

Niż

 flags &= 0xFFFFFFFE;

I pierwsza publiczność będzie po prostu zrzędzić, ale trudno sobie wyobrazić, że nie będą w stanie grok i utrzymać nową składnię. Trudniej to spieprzyć. Nie będzie nowych błędów, ponieważ newb będzie łatwiej utrzymać kod. Będziesz miał wykłady o tym ,jak " za moich czasów potrzebowałeś pewnej ręki i namagnesowana igła do ustawiania bitów... nie mieliśmy nawet bitmasków!"(thanks XKCD ).

Więc zdecydowanie polecam użycie pól nad bitmaskami, aby zabezpieczyć swój kod newb-safe.

 18
Author: Doug T.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-25 17:12:39

Użycie Unii Ma nieokreślone zachowanie zgodnie ze standardem ANSI C, a zatem nie powinno być używane (lub przynajmniej nie powinno być uważane za przenośne).

Z normy ISO/IEC 9899:1999 (C99) :

Załącznik J - Zagadnienia Związane Z Przenośnością:

1 następujące są nieokreślone:

- wartość bajtów wypełnienia podczas przechowywania wartości w strukturach lub związkach (6.2.6.1).

- wartość innego członka Związku niż ostatni zapisany w (6.2.6.1).

6.2.6.1 - pojęcia językowe - Reprezentacja typów - ogólne:

6 Gdy wartość jest przechowywana w obiekcie o strukturze lub typie Unii, w tym w elemencie obiekt, bajty reprezentacji obiektu, które odpowiadają dowolnym bajtom wypełnienia, przyjmują nieokreślone wartości.[42]) wartość struktury lub obiektu Unii nigdy nie jest pułapką reprezentacji, nawet jeśli wartość członka struktury lub przedmiotu związku może być przedstawienie pułapki.

7 gdy wartość jest przechowywana w elemencie obiektu typu union, bajty obiektu reprezentacja, która nie odpowiada temu członkowi, ale odpowiada innym członkom przyjmij nieokreślone wartości.

Jeśli więc chcesz zachować korespondencję bitfield ↔ integer i zachować przenośność, zdecydowanie sugeruję użycie metody bitmasking, która w przeciwieństwie do linkowanego posta na blogu jest , a nie słabą praktyką.

 14
Author: Juliano,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-27 17:39:10

Co jest w podejściu bitfield, które sprawia, że się marudzisz?

Obie techniki mają swoje miejsce, a jedyną decyzją, którą mam użyć, jest:

Dla prostych" jednorazowych " bitów, używam operatorów bitowych bezpośrednio.

W przypadku bardziej złożonych-np. map rejestru sprzętowego, podejście bitfield wygrywa.

  • pola bitowe są bardziej zwięzłe w użyciu (kosztem / nieco / więcej werbalność do pisania.
  • pola bitowe są bardziej wytrzymały (jaki rozmiar jest " int", anyway)
  • pola bitowe są zwykle tylko tak szybkie jak operatory bitowe.
  • [9]}pola bitowe są bardzo potężne, gdy mieć mieszankę jednego i wielu bitów pola, oraz wydobywanie pole wielobitowe obejmuje ładunki ręczne zmiany.
  • pola bitowe są skutecznie samokontroli. Przez definiowanie struktury, a zatem nazwanie elementów, wiem co to jest tak miało być.
  • pola bitowe również bezproblemowo obsługują struktury większe niż pojedyncze int.
  • W przypadku operatorów bitowych typową (złą) praktyką jest zbiór #definiuje dla masek bitowych.

  • Jedynym zastrzeżeniem w bitfields jest upewnienie się, że kompilator naprawdę spakował obiekt do pożądanego rozmiaru. Nie pamiętam, czy jest to zdefiniowane przez standard, więc assert(sizeof (myStruct) = = N) jest użytecznym sprawdzeniem.

 10
Author: Roddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-25 16:00:09

Tak czy inaczej, pola bitowe były używane w oprogramowaniu GNU od dziesięcioleci i nie wyrządziło im to żadnej szkody. Lubię je jako parametry do funkcji.

Argumentowałbym, że pola bitowe są konwencjonalnymi w przeciwieństwie do struktur. Każdy wie, jak i wartości ustawić różne opcje off i kompilator sprowadza to do bardzo wydajne bitowe operacje na CPU.

Pod warunkiem, że używasz masek i testów w prawidłowy sposób, abstrakcje dostarczane przez kompilator powinien być solidny, prosty, czytelny i czysty.

Kiedy potrzebuję zestawu włączników / wyłączników, będę nadal ich używać w C.

 6
Author: Aiden Bell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-25 16:14:51

The blog post odnosisz się do wzmianek raw union field jako alternatywnej metody dostępu dla bitfieldów.

Cele, dla których autor postu na blogu używał raw są w porządku, jednak jeśli planujesz użyć go do czegokolwiek innego (np. serializacji pól bitowych, ustawiania/sprawdzania poszczególnych bitów), katastrofa czeka na Ciebie za rogiem. Kolejność bitów w pamięci jest zależna od architektury, a zasady wypełniania pamięci różnią się w zależności od kompilatora (zobacz wikipedia ), tak więc dokładna pozycja każdego bitfielda może być różna, innymi słowy nigdy nie można być pewnym, który bit raw odpowiada każdemu bitfieldowi.

Jeśli jednak nie planujesz mieszać, lepiej wyjmij raw i będziesz bezpieczny.

 6
Author: qrdl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-26 07:27:43

Cóż, nie możesz się pomylić z mapowaniem struktury, ponieważ oba pola są dostępne, mogą być używane zamiennie.

Jedną z zalet pól bitowych jest to, że można łatwo agregować opcje:

mask = USER|FORCE|ZERO|COMPAT;

vs

flags.user = true;
flags.force = true;
flags.zero = true;
flags.compat = true;

W niektórych środowiskach, takich jak radzenie sobie z opcjami protokołów, może stać się dość stare, gdy trzeba indywidualnie ustawiać opcje lub używać wielu parametrów do przesyłania Stanów pośrednich w celu uzyskania ostatecznego wyniku.

Ale czasami ustawianie flagi.bla i posiadanie listy popup w IDE jest świetny, zwłaszcza jeśli lubisz mnie i nie pamiętasz nazwy flagi, którą chcesz ustawić bez ciągłego odwoływania się do listy.

Ja osobiście czasami będę unikał deklarowania typów logicznych, ponieważ w pewnym momencie skończę z błędnym wrażeniem, że pole, które właśnie przełączałem, nie było zależne (myślę, że współbieżność wielowątkowa) od statusu r/w innych "pozornie" niepowiązanych pól, które zdarzyły się dzielić to samo 32-bitowe słowo.

Mój Głos polega na tym, że zależy to od kontekst sytuacji, a w niektórych przypadkach oba podejścia mogą się dobrze wypracować.

 6
Author: Einstein,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-27 21:37:29

W C++ wystarczy użyć std::bitset<N>.

 5
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-26 01:01:53

Jest podatny na błędy, tak. Widziałem wiele błędów w tego rodzaju kodzie, głównie dlatego, że niektórzy ludzie uważają, że powinni mieszać z nim i logiką biznesową w całkowicie niezorganizowany sposób, tworząc koszmary konserwacyjne. Myślą, że" prawdziwi " Programiści potrafią pisać value |= mask; , value &= ~mask; albo jeszcze gorzej w jakimkolwiek miejscu, i to jest po prostu ok. Nawet lepiej, jeśli wokół jest jakiś operator Przyrostowy, kilka memcpy'S, rzutów wskaźnikowych i cokolwiek niejasnego i podatnego na błędy składni się dzieje ich umysł w tym czasie. Oczywiście nie ma potrzeby, aby być konsekwentnym i można przerzucać bity na dwa lub trzy różne sposoby, rozmieszczone losowo.

Moja rada brzmiałaby:

  1. Enkapsulate this - - - - in a class, with methods such as SetBit(...) and ClearBit(...). (Jeśli nie masz klas w C, w module.) Kiedy już przy tym jesteś, możesz udokumentować ich zachowanie.
  2. test jednostkowy tej klasy lub modułu.
 4
Author: Daniel Daranas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-11 13:01:59

Twoja pierwsza metoda jest lepsza, IMHO. Dlaczego zaciemniać problem? Trochę bałaganu to naprawdę podstawowa rzecz. C zrobił to dobrze. Endianess nie ma znaczenia. Jedyne, co rozwiązanie Unii robi, to wymieniać rzeczy. 11 może być tajemnicze, ale # zdefiniowany do znaczącej nazwy lub enum'ed powinien wystarczyć.

Programiści, którzy nie radzą sobie z podstawami takimi jak"/&^~", są prawdopodobnie w niewłaściwej pracy.

 2
Author: xcramps,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-25 17:39:53

Gdy wygoogluję dla "operatorów c"

Pierwsze trzy strony to:

Http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B http://h30097.www3.hp.com/docs/base_doc/DOCUMENTATION/V40F_HTML/AQTLTBTE/DOCU_059.HTM http://www.cs.mun.ca / ~michael/c/op.html

.. więc myślę, że ten argument o ludziach nowych w języku jest trochę głupi.

 2
Author: San Jacinto,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-25 17:57:10

Prawie zawsze używam operacji logicznych z maską bitową, bezpośrednio lub jako makro. np.

 #define  ASSERT_GPS_RESET()                    { P1OUT &= ~GPS_RESET ; }

Nawiasem mówiąc, twoja definicja Unii w pierwotnym pytaniu nie zadziała na mojej kombinacji procesor / kompilator. Typ int ma tylko 16 bitów szerokości, a definicje bitfield to 32. Aby uczynić go nieco bardziej przenośnym, musisz zdefiniować nowy 32-bitowy typ, który możesz następnie mapować do wymaganego typu bazowego na każdej docelowej architekturze w ramach przenoszenia ćwiczenia. W moim przypadku

typedef   unsigned long int     uint32_t

I w oryginalnym przykładzie

typedef unsigned int uint32_t

typedef union {
        struct {
                boolean_t user:1;
                boolean_t zero:1;
                boolean_t force:1;
                int :28;                /* unused */
                boolean_t compat:1;     /* bit 31 */
        };
        uint32_t raw;
} flags_t;

Nakładana liczba całkowita powinna być również niepodpisana.

 2
Author: ʎəʞo uɐɪ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-26 09:27:58

Cóż, przypuszczam, że to jest jeden sposób na zrobienie tego, ale zawsze wolałbym zachować to proste .

Kiedy już się przyzwyczaisz, używanie masek jest proste, jednoznaczne i przenośne.

Pola bitowe są proste, ale nie są przenośne bez konieczności wykonywania dodatkowej pracy.

Jeśli kiedykolwiek będziesz musiał napisać kod zgodny z MISRA , wytyczne MISRA brwi na bitfieldach, związkach i wielu, wielu innych aspektach C, aby uniknąć undefined lub zachowanie zależne od implementacji.

 2
Author: Steve Melnikoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-27 15:48:18

Ogólnie rzecz biorąc, ten, który jest łatwiejszy do odczytania i zrozumienia, jest Tym, który jest również łatwiejszy do utrzymania. Jeśli masz współpracowników, którzy są nowi w C," bezpieczniejsze " podejście będzie prawdopodobnie łatwiejsze do zrozumienia.

 1
Author: Mike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-25 16:21:09

Pola bitowe są świetne, z tym wyjątkiem, że operacje manipulacji bitami nie są atomowe, a zatem mogą prowadzić do problemów w aplikacjach wielowątkowych.

Na przykład można założyć, że makro:

#define SET_BIT(val, bitIndex) val |= (1 << bitIndex)

Definiuje operację atomową, ponieważ |= jest jedną instrukcją. Jednak zwykły kod generowany przez kompilator nie będzie próbował uczynić / = atomowym.

Więc jeśli wiele wątków wykonuje różne operacje bitów zestawu, jedna z operacji bitów zestawu może być fałszywa. Ponieważ oba wątki będą wykonanie:

  thread 1             thread 2
  LOAD field           LOAD field
  OR mask1             OR mask2
  STORE field          STORE field

Wynikiem może być pole '= pole lub mask1 lub mask2 (intented), lub wynikiem może być pole' = pole lub mask1 (not intented) lub wynikiem może być pole' = pole lub mask2 (not intented).

 1
Author: j4n bur53,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-30 00:43:54