C Language
Wyściółka i pakowanie struktury
Szukaj…
Wprowadzenie
Domyślnie kompilatory C układają struktury, dzięki czemu dostęp do każdego elementu można uzyskać szybko, bez ponoszenia kar za „niezaangażowany dostęp, problem z maszynami RISC, takimi jak DEC Alpha, i niektórymi procesorami ARM.
W zależności od architektury procesora i kompilatora struktura może zajmować więcej miejsca w pamięci niż suma rozmiarów elementów składowych. Kompilator może dodawać wypełnienia między elementami lub na końcu struktury, ale nie na początku.
Pakowanie zastępuje domyślne wypełnienie.
Uwagi
Eric Raymond ma artykuł na temat The Lost Art of C Structure Packing, który jest przydatny do czytania.
Konstrukcje do pakowania
Domyślnie struktury są wypełnione w C. Jeśli chcesz uniknąć tego zachowania, musisz jawnie o to poprosić. W GCC jest to __attribute__((__packed__))
. Rozważ ten przykład na komputerze 64-bitowym:
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
Struktura zostanie automatycznie wypełniona, aby uzyskać wyrównanie do 8-byte
i będzie wyglądać następująco:
struct foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
char pad[7]; /* 7 bytes added by compiler */
long x; /* 8 bytes */
};
Więc sizeof(struct foo)
da nam 24
zamiast 17
. Stało się tak, ponieważ 64-bitowy kompilator odczytuje / zapisuje z / do pamięci w 8 bajtach słowa na każdym kroku i jest oczywisty przy próbie zapisu char c;
jeden bajt w pamięci pobrano pełne 8 bajtów (tj. słowo) i zużywa tylko pierwszy bajt, a jego siedem kolejnych bajtów pozostaje pustych i niedostępnych dla żadnej operacji odczytu i zapisu dla wypełniania struktury.
Pakowanie struktury
Ale jeśli dodasz atrybut packed
, kompilator nie doda dopełniania:
struct __attribute__((__packed__)) foo {
char *p; /* 8 bytes */
char c; /* 1 byte */
long x; /* 8 bytes */
};
Teraz sizeof(struct foo)
zwróci 17
.
Stosuje się ogólnie upakowane struktury:
- Aby zaoszczędzić miejsce.
- Aby sformatować strukturę danych do transmisji przez sieć, nie zależąc od każdego wyrównania architektury każdego węzła sieci.
Należy wziąć pod uwagę, że niektóre procesory, takie jak ARM Cortex-M0, nie umożliwiają nieograniczonego dostępu do pamięci; w takich przypadkach pakowanie struktur może prowadzić do nieokreślonego zachowania i spowodować awarię procesora.
Wypełnienie struktury
Załóżmy, że ta struct
jest zdefiniowana i skompilowana za pomocą 32-bitowego kompilatora:
struct test_32 {
int a; // 4 byte
short b; // 2 byte
int c; // 4 byte
} str_32;
Możemy oczekiwać, że ta struct
zajmie tylko 10 bajtów pamięci, ale drukując sizeof(str_32)
, widzimy, że używa 12 bajtów.
Stało się tak, ponieważ kompilator wyrównuje zmienne w celu szybkiego dostępu. Częstym wzorcem jest to, że gdy typ podstawowy zajmuje N bajtów (gdzie N jest potęgą 2, takich jak 1, 2, 4, 8, 16 - i rzadko większy), zmienna powinna być wyrównana na granicy N-bajtów ( wielokrotność N bajtów).
Dla struktury pokazanej z sizeof(int) == 4
i sizeof(short) == 2
wspólny układ to:
-
int a;
przechowywane z przesunięciem 0; rozmiar 4. -
short b;
przechowywane z przesunięciem 4; rozmiar 2. - nienazwane wypełnienie przy przesunięciu 6; rozmiar 2.
-
int c;
przechowywane z przesunięciem 8; rozmiar 4.
Zatem struct test_32
zajmuje 12 bajtów pamięci. W tym przykładzie nie ma końcowego dopełnienia.
Kompilator zapewni, że wszelkie zmienne struct test_32
będą przechowywane, zaczynając od granicy 4-bajtowej, dzięki czemu elementy w strukturze zostaną odpowiednio wyrównane w celu szybkiego dostępu. Funkcje alokacji pamięci, takie jak malloc()
, calloc()
i realloc()
są wymagane, aby zapewnić, że zwracany wskaźnik jest wystarczająco dobrze wyrównany do użycia z dowolnym typem danych, więc dynamicznie alokowane struktury również będą odpowiednio wyrównane.
Możesz skończyć z dziwnymi sytuacjami, takimi jak 64-bitowy procesor Intel x86_64 (np. Intel Core i7 - Mac z systemem MacOS Sierra lub Mac OS X), gdzie kompilatory umieszczają double
wyrównane na Granica 4 bajtów; ale na tym samym sprzęcie podczas kompilacji w trybie 64-bitowym kompilatory umieszczają double
wyrównane na 8-bajtowej granicy.