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.



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