Czytałem, że kolejność pól bitowych w strukturze zależy od platformy. A jeśli użyję różnych opcji pakowania specyficznych dla kompilatora, czy ta gwarancja będzie przechowywana we właściwej kolejności, w jakiej są zapisywane? Na przykład:
struct Message
{
unsigned int version : 3;
unsigned int type : 1;
unsigned int id : 5;
unsigned int data : 6;
} __attribute__ ((__packed__));
Na procesorze Intela z kompilatorem GCC pola zostały ułożone w pamięci tak, jak są pokazane. Message.version
był pierwszymi 3 bitami w buforze, a Message.type
następnie. Jeśli znajdę równoważne opcje pakowania struktury dla różnych kompilatorów, czy będzie to wieloplatformowe?
c++
c
bit-manipulation
endianness
bit
dewald
źródło
źródło
Odpowiedzi:
Nie, nie będzie w pełni przenośny. Opcje pakowania struktur są rozszerzeniami i same w sobie nie są w pełni przenośne. Oprócz tego, C99 §6.7.2.1, paragraf 10 mówi: „Kolejność przydzielania pól bitowych w jednostce (od wysokiego do niskiego lub od niskiego do wysokiego) jest określona przez implementację”.
Nawet pojedynczy kompilator może inaczej rozłożyć pole bitowe, na przykład w zależności od endianness platformy docelowej.
źródło
packed
egzekwować zamawiania: stackoverflow.com/questions/1756811/... jak wymusić kolejność bitów: stackoverflow.com/questions/6728218/gcc-compiler-bit-orderPola bitowe różnią się znacznie od kompilatora do kompilatora, przepraszam.
W przypadku GCC maszyny big endian układają bity jako pierwszy, a maszyny little endian układają bity jako pierwszy.
K&R mówi: „Sąsiadujące [bit-] elementy składowe pól struktur są pakowane do jednostek pamięci zależnych od implementacji w kierunku zależnym od implementacji. Kiedy pole następujące po innym polu nie będzie pasować ... może zostać podzielone na jednostki lub jednostka może być wypełnione. Nienazwane pole o szerokości 0 wymusza to wypełnienie ... ”
Dlatego jeśli potrzebujesz niezależnego od komputera układu binarnego, musisz to zrobić samodzielnie.
To ostatnie stwierdzenie odnosi się również do pól innych niż bitowe ze względu na wypełnienie - jednak wydaje się, że wszystkie kompilatory mają jakiś sposób na wymuszenie pakowania bajtów w strukturze, jak widzę już odkryłeś dla GCC.
źródło
Należy unikać pól bitowych - nie są one zbyt przenośne między kompilatorami, nawet dla tej samej platformy. ze standardu C99 6.7.2.1/10 - „Specyfikatory struktury i unii” (podobne sformułowanie występuje w standardzie C90):
Nie możesz zagwarantować, czy pole bitowe `` obejmie '' granicę int, czy nie, i nie możesz określić, czy pole bitowe zaczyna się od dolnego końca int czy górnego końca int (jest to niezależne od tego, czy procesor jest big-endian lub little-endian).
Preferuj maski bitowe. Użyj wbudowanych (lub nawet makr), aby ustawić, wyczyścić i przetestować bity.
źródło
_BIT_FIELDS_LTOH
oraz_BIT_FIELDS_HTOL
endianness mówi o porządkach bajtów, a nie o porządkach bitowych. Obecnie istnieje 99% pewności, że zamówienia bitów są stałe. Jednak w przypadku korzystania z pól bitowych endianness należy traktować jako liczbę. Zobacz poniższy przykład.
#include <stdio.h> typedef struct tagT{ int a:4; int b:4; int c:8; int d:16; }T; int main() { char data[]={0x12,0x34,0x56,0x78}; T *t = (T*)data; printf("a =0x%x\n" ,t->a); printf("b =0x%x\n" ,t->b); printf("c =0x%x\n" ,t->c); printf("d =0x%x\n" ,t->d); return 0; } //- big endian : mips24k-linux-gcc (GCC) 4.2.3 - big endian a =0x1 b =0x2 c =0x34 d =0x5678 1 2 3 4 5 6 7 8 \_/ \_/ \_____/ \_____________/ a b c d // - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2 a =0x2 b =0x1 c =0x34 d =0x7856 7 8 5 6 3 4 1 2 \_____________/ \_____/ \_/ \_/ d c b a
źródło
Prawdopodobnie przez większość czasu, ale nie stawiaj na to farmy, bo jeśli się pomylisz, dużo stracisz.
Jeśli naprawdę potrzebujesz identycznych informacji binarnych, będziesz musiał utworzyć pola bitowe z maskami bitowymi - np. Użyjesz skrótu bez znaku (16 bitów) dla Message, a następnie utwórz rzeczy takie jak versionMask = 0xE000, aby reprezentowały trzy najwyższe bity.
Podobny problem występuje z wyrównaniem wewnątrz struktur. Na przykład procesory Sparc, PowerPC i 680x0 to wszystkie procesory typu big-endian, a typową wartością domyślną kompilatorów Sparc i PowerPC jest wyrównywanie elementów struktur na granicach 4-bajtowych. Jednak jeden kompilator, którego użyłem dla 680x0, wyrównał tylko do granic 2-bajtowych - i nie było opcji zmiany wyrównania!
Tak więc dla niektórych struktur rozmiary Sparc i PowerPC są identyczne, ale mniejsze na 680x0, a niektóre elementy są w różnych przesunięciach pamięci w strukturze.
Był to problem z jednym projektem, nad którym pracowałem, ponieważ proces serwera działający na Sparc odpytywał klienta i dowiadywał się, że jest to big-endian i zakładał, że może po prostu wypuścić struktury binarne w sieci, a klient sobie z tym poradzi. I to działało dobrze na klientach PowerPC i powodowało duże awarie na klientach 680x0. Nie napisałem kodu, a znalezienie problemu zajęło trochę czasu. Ale kiedy już to zrobiłem, łatwo było to naprawić.
źródło
Dzięki @BenVoigt za bardzo przydatny komentarz na początku
Źródło Linux robi użyć pola bitowego, aby dopasować się do konstrukcji zewnętrznej: /usr/include/linux/ip.h ma ten kod na pierwszy bajt datagramu IP
struct iphdr { #if defined(__LITTLE_ENDIAN_BITFIELD) __u8 ihl:4, version:4; #elif defined (__BIG_ENDIAN_BITFIELD) __u8 version:4, ihl:4; #else #error "Please fix <asm/byteorder.h>" #endif
Jednak w świetle twojego komentarza rezygnuję z prób, aby to działało dla wielobajtowego pola bitowego frag_off .
źródło
Oczywiście najlepszą odpowiedzią jest użycie klasy, która odczytuje / zapisuje pola bitowe jako strumień. Użycie struktury pola bitowego C po prostu nie jest gwarantowane. Nie wspominając o tym, że używanie tego w prawdziwym kodowaniu jest uważane za nieprofesjonalne / leniwe / głupie.
źródło