Układ pamięci Struct w C

85

Mam tło C #. Jestem bardzo nowicjuszem w języku niskiego poziomu, takim jak C.

W języku C # structpamięć jest domyślnie układana przez kompilator. Kompilator może niejawnie zmienić kolejność pól danych lub wstawić dodatkowe bity między polami. Musiałem więc określić specjalny atrybut, aby zastąpić to zachowanie dla dokładnego układu.

AFAIK, C nie zmienia kolejności ani nie wyrównuje układu pamięci structdomyślnie. Słyszałem jednak, że jest mały wyjątek, który jest bardzo trudny do znalezienia.

Jakie jest zachowanie układu pamięci w C? Co powinno zostać ponownie uporządkowane / wyrównane, a czego nie?

eonil
źródło

Odpowiedzi:

110

W C kompilator może dyktować pewne wyrównanie dla każdego typu pierwotnego. Zazwyczaj wyrównanie jest wielkością typu. Ale jest to całkowicie zależne od implementacji.

Wprowadzane są bajty wypełniające, dzięki czemu każdy obiekt jest odpowiednio wyrównany. Zmiana kolejności jest niedozwolona.

Prawdopodobnie każdy zdalnie nowoczesny kompilator implementuje, #pragma packco pozwala na kontrolę nad wypełnianiem i pozostawia programiście zgodność z ABI. (Jest to jednak ściśle niestandardowe).

Z C99 §6.7.2.1:

12 Każdy element członkowski struktury lub obiektu unii niebędący polem bitowym jest wyrównany w sposób zdefiniowany w implementacji, odpowiedni dla jego typu.

13 W obrębie obiektu strukturalnego elementy składowe niebędące polami bitowymi i jednostki, w których znajdują się pola bitowe, mają adresy, które rosną w kolejności, w jakiej są deklarowane. Wskaźnik do obiektu struktury, odpowiednio przekonwertowany, wskazuje na jego początkowy element członkowski (lub jeśli ten element jest polem bitowym, to na jednostkę, w której się znajduje) i na odwrót. W obiekcie struktury może występować nienazwane wypełnienie, ale nie na jego początku.

Potatoswatter
źródło
1
Niektóre kompilatory (np. GCC) implementują ten sam efekt, #pragma packale z bardziej szczegółową kontrolą nad semantyką.
Chris Lutz
21
Jestem zaskoczony, widząc przeciw. Czy ktoś może wskazać błąd?
Potatoswatter
2
C11 również ma _Alignas.
idmean
117

Jest to specyficzne dla implementacji, ale w praktyce zasada (w przypadku braku #pragma packlub podobnych) to:

  • Składowe struktury są przechowywane w kolejności, w jakiej zostały zadeklarowane. (Jest to wymagane przez standard C99, jak wspomniano wcześniej).
  • W razie potrzeby dopełnienie jest dodawane przed każdym składnikiem struktury, aby zapewnić prawidłowe wyrównanie.
  • Każdy typ pierwotny T wymaga wyrównania sizeof(T)bajtów.

Tak więc, biorąc pod uwagę następującą strukturę:

struct ST
{
   char ch1;
   short s;
   char ch2;
   long long ll;
   int i;
};
  • ch1 jest na przesunięciu 0
  • bajt wypełniający jest wstawiany w celu wyrównania ...
  • s przy odsunięciu 2
  • ch2 jest na przesunięciu 4, bezpośrednio po s
  • 3 bajty wypełniające są wstawiane w celu wyrównania ...
  • ll przy odsunięciu 8
  • i jest na przesunięciu 16, tuż za ll
  • Na końcu dodawane są 4 bajty wypełniające, tak że ogólna struktura jest wielokrotnością 8 bajtów. Sprawdziłem to w systemie 64-bitowym: systemy 32-bitowe mogą zezwalać strukturom na wyrównanie 4-bajtowe.

Więc sizeof(ST)jest 24.

Można go zmniejszyć do 16 bajtów, zmieniając układ elementów, aby uniknąć wypełnienia:

struct ST
{
   long long ll; // @ 0
   int i;        // @ 8
   short s;      // @ 12
   char ch1;     // @ 14
   char ch2;     // @ 15
} ST;
dan04
źródło
3
Jeśli to konieczne, wypełnienie jest dodawane przed ... Bardziej jak po. Najlepiej dodaj ostatniego charczłonka do swojego przykładu.
Deduplicator
9
Typ pierwotny niekoniecznie wymaga wyrównania sizeof(T)bajtów. Na przykład doubletypowa architektura 32-bitowa ma 8 bajtów, ale często wymaga tylko 4-bajtowego wyrównania . Ponadto wyściółka na końcu struktury tylko dopasowuje się do wyrównania najszerszego elementu konstrukcji. Na przykład struktura składająca się z 3 zmiennych typu char może nie mieć wypełnienia.
Matt
1
@ dan04, czy byłoby dobrą praktyką układanie struktur w malejącej kolejności sizeof (T). Czy miałoby to jakieś wady?
RohitMat
11

Możesz zacząć od przeczytania artykułu poświęconego dopasowaniu struktury danych w Wikipedii, aby lepiej zrozumieć wyrównanie danych.

Z artykułu w Wikipedii :

Wyrównanie danych oznacza umieszczenie danych w przesunięciu pamięci równym pewnej wielokrotności rozmiaru słowa, co zwiększa wydajność systemu ze względu na sposób, w jaki procesor obsługuje pamięć. Aby wyrównać dane, może być konieczne wstawienie kilku bezsensownych bajtów między końcem ostatniej struktury danych a początkiem następnej, czyli wypełnienia struktury danych.

Od 6.54.8 Pragmy pakowania struktury w dokumentacji GCC:

Aby zapewnić zgodność z kompilatorami Microsoft Windows, GCC obsługuje zestaw dyrektyw #pragma, które zmieniają maksymalne wyrównanie elementów składowych struktur (innych niż pola bitowe o zerowej szerokości), unii i klas zdefiniowanych później. Wartość n poniżej zawsze musi być małą potęgą dwóch i określa nowe wyrównanie w bajtach.

  1. #pragma pack(n) po prostu ustawia nowe wyrównanie.
  2. #pragma pack() ustawia wyrównanie na to, które obowiązywało podczas uruchamiania kompilacji (zobacz także opcję wiersza poleceń -fpack-struct [=] zobacz Opcje generowania kodu).
  3. #pragma pack(push[,n]) wypycha bieżące ustawienie wyrównania na stosie wewnętrznym, a następnie opcjonalnie ustawia nowe wyrównanie.
  4. #pragma pack(pop)przywraca ustawienie wyrównania do ustawienia zapisanego na górze stosu wewnętrznego (i usuwa ten wpis stosu). Zauważ, że #pragma pack([n])nie ma to wpływu na ten wewnętrzny stos; w ten sposób możliwe jest #pragma pack(push) śledzenie wielu #pragma pack(n) instancji i sfinalizowanie przez jedną #pragma pack(pop).

Niektóre cele, np. I386 i powerpc, obsługują ms_struct, #pragmaktóry przedstawia strukturę zgodnie z dokumentacją __attribute__ ((ms_struct)).

  1. #pragma ms_struct on włącza układ zadeklarowanych struktur.
  2. #pragma ms_struct off wyłącza układ zadeklarowanych struktur.
  3. #pragma ms_struct reset wraca do układu domyślnego.
jschmier
źródło
Dzięki za opiekę. Zmodyfikowałem pytanie zgodnie z twoimi wskazówkami.
eonil