2010-05-01 17 views
61

Mam tła C#. Bardzo początkujący dla języka niskiego poziomu, takiego jak C.C układ pamięci typu struct?

W języku C#, pamięć struct określona domyślnie przez kompilator. Kompilator może ponownie uporządkować pola danych lub niejawnie wstawić dodatkowe bity między polami. Musiałem więc podać jakiś specjalny atrybut, aby zastąpić to zachowanie dla dokładnego układu.

AFAIK, C nie zmienia domyślnie ani nie wyrównuje układu pamięci struktury. Ale słyszałem, że jest kilka wyjątków, które bardzo trudno znaleźć.

Co to jest zachowanie układu pamięci C? (co powinno być ponownie zamówione/wyrównane, a nie)

Odpowiedz

81

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

Wprowadza się bajty dopełniające, aby każdy obiekt był odpowiednio wyrównany. Zmiana kolejności nie jest dozwolona.

Prawdopodobnie każdy zdalnie nowoczesny kompilator implementuje #pragma pack, który pozwala kontrolować wypełnienie i pozostawia programistom zgodność z ABI. (Jest ściśle nietypowa, chociaż).

od C99 §6.7.2.1:

12 Każdy człon bez bitów pola obiektu struktury związków lub jest wyrównany w sposób implementation- zdefiniowane odpowiedni do swojego typu.

13 W ciągu struktury obiektu, non-bitowe pole członkowie i jednostki, w których -bitowe pola przebywania mieć adresy wzrost w kolejności, w jakiej zostały zadeklarowane. Wskaźnik do obiektu obiektu, odpowiednio przekonwertowany, wskazuje na jego początkowego członka (lub jeśli ten członek jest polem bitowym, a następnie do jednostki w , której rezyduje) i na odwrót. Wewnątrz obiektu struktury może znajdować się wypełnienie bez nazwy, ale nie na początku jego .

+0

Niektóre kompilatory (tj. GCC) realizują ten sam efekt co '#pragma pack', ale z bardziej szczegółową kontrolą nad semantyką. –

+14

Jestem zaskoczony, widząc upadek. Czy ktoś może wskazać błąd? – Potatoswatter

+0

Dzięki za opiekę. Zaktualizowałem pytanie podczas prowadzenia. – Eonil

8

Możesz zacząć od przeczytania data structure alignment wikipedia article, aby lepiej zrozumieć wyrównanie danych.

Z wikipedia article:

wyrównanie danych oznacza wprowadzanie danych w pamięci offsetowego równa jakiejś wielokrotności rozmiaru tekstu, co zwiększa wydajność systemu ze względu na sposób CPU obsługuje pamięć. Aby wyrównać dane, może być konieczne wstawienie niektórych bezsensownych bajtów między końcem ostatniej struktury danych a początkiem następnej, która jest dopełnieniem struktury danych.

Od 6.54.8 Structure-Packing Pragmas dokumentacji GCC:

Dla kompatybilności z Microsoft kompilatory Windows GCC obsługuje zestaw dyrektyw #pragma które zmieniają maksymalne wyrównanie członków struktur (innego niż zero szerokości bitfields), związki i klasy określone później. Wymagana jest zawsze n wartość , która ma zawsze być małą mocą wynoszącą i określa nowe wyrównanie w bajtach.

  1. Pakiet pragma (n) po prostu ustawia nowe wyrównanie.

  2. Pragma paczka() ustawia wyrównanie do tego, który był w

    efektu podczas kompilacji zaczął (patrz dowodzić również opcja wiersza -fpack-struct [=] patrz Kod Opcje gen).
  3. pragmy paczka (Push [n]) popycha bieżące ustawienia wyrównania na

    wewnętrznego stosu i następnie ewentualnie ustawia nowy wyrównania.
  4. Pragma paczka (pop) przywraca ustawienie wyrównanie do jednego zapisanego w

    górze wewnętrznego stosu (i usuwa ten wpis stosu). Zauważ, że enter code here #pragma pack ([n]) nie ma wpływu na ten wewnętrzny stos; w ten sposób można uzyskać pakiet #pragma (push) , a następnie wiele instancji #pragma pack (n) i sfinalizowanych przez pojedynczy pakiet #pragma pack (pop).

Niektóre cele, np.i386 i powerpc, obsługują ms_struct #pragma, którą określa struktura jako udokumentowany __attribute__ ((ms_struct)).

  1. pragma ms_struct po włączeniu układu deklarowanych struktur.

  2. pragma ms_struct off wyłącza układ zadeklarowanych struktur.

  3. pragma ms_struct reset powraca do domyślnego układu.

+0

Dzięki za opiekę. Zmodyfikowałem pytanie, kiedy prowadziłeś. – Eonil

2

W języku C struktury są rozmieszczone niemal dokładnie tak, jak podano w kodzie. Podobne do C# 's StructLayout.Sequential.

Jedyna różnica polega na wyrównaniu elementów. To nigdy nie zmienia kolejności elementów danych w strukturze, ale może zmienić rozmiar struktury poprzez wstawienie bajtów "pad" w środku struktury. Powodem tego jest upewnienie się, że każdy z członków zaczyna na granicy (zwykle 4 lub 8 bajtów).

Na przykład

struct mystruct { 
    int a; 
    short int b; 
    char c; 
}; 

Rozmiary tej struktury jest zazwyczaj 12 bajtów (4 dla każdego użytkownika). Wynika to z faktu, że większość kompilatorów domyślnie sprawia, że ​​każdy członek ma taki sam rozmiar, jak największy w strukturze. Więc char zajmie 4 bajty zamiast jednego. Ale bardzo ważne jest, aby pamiętać, że sizeof (mystruct :: c) będzie nadal o 1, ale sizeof (mystruct) będzie 12.

Może być trudno przewidzieć, w jaki sposób struktura zostanie dopełniona/wyrównana przez kompilator .Większość będzie domyślna, tak jak to wyjaśniłem powyżej, niektóre domyślnie nie będą dopełniać/wyrównywać (również czasami nazywane "spakowanymi").

Metoda zmiany tego zachowania jest zależna od kompilatora, w języku nie ma nic określającego sposób obsługi tego zachowania. W MSVC użyjesz #pragma pack(1), aby wyłączyć wyrównanie (1 mówi wyrównać wszystko na 1 granicy bajtów). W GCC użyjesz __attribute__((packed)) w definicji struktury. Zapoznaj się z dokumentacją kompilatora, aby zobaczyć, co robi domyślnie i jak zmienić to zachowanie.

+3

Uh, 'sizeof (struct mystruct)' drukuje 8 w moim systemie. C nie wyrównuje wszystkich elementów do wyrównania największego elementu, wyrównuje wszystkich elementów do ich wyrównania, a następnie wyrównuje strukturę do wyrównania największego elementu. –

+0

Uh, jak powiedziałem, zależy to od kompilatora. – SoapBox

+11

Soapbox: Nie, jeśli żaden kompilator nie robi tego w ten sposób. – Potatoswatter

88

To wdrożenie specyficznych, ale w praktyce zasady (w przypadku braku #pragma pack lub podobnym) jest:

  • członkowie struct przechowywane są w kolejności, w jakiej zostały zgłoszone. (Jest to wymagane przez standard C99, jak wspomniano wcześniej).
  • W razie potrzeby przed każdym elementem strukturalnym dodaje się dopełnienie, aby zapewnić prawidłowe wyrównanie.
  • Każdy typ pierwotny T wymaga wyrównania bajtów sizeof(T).

więc, biorąc pod uwagę następujące struct:

struct ST 
{ 
    char ch1; 
    short s; 
    char ch2; 
    long long ll; 
    int i; 
}; 
  • ch1 jest pod offsetem 0
  • bajt wyściółka jest włożona do wyrównania ...
  • s na przesunięcie 2
  • ch2 znajduje się w offsecie 4, natychmiast po s
  • 3 bajty dopełniające są wstawiane, aby wyrównać ...
  • ll na przesunięcie 8
  • I jest w offsecie 16, tuż po ll
  • 4 bajty wypełniające są dodawane na końcu tak, że ogólna struktura jest wielokrotnością 8 bajtów. Sprawdziłem to w systemie 64-bitowym: systemy 32-bitowe mogą zezwalać na strukturę 4-bajtową.

Więc sizeof(ST) jest 24.

To może być zmniejszona do 16 bajtów poprzez zmianę członków uniknąć padding:

struct ST 
{ 
    long long ll; // @ 0 
    int i;  // @ 8 
    short s;  // @ 12 
    char ch1;  // @ 14 
    char ch2;  // @ 15 
} ST; 
+3

Jeśli to konieczne, dopełnienie zostanie dodane przed ...Bardziej jak po. Najlepiej dodaj do swojego przykładu ostatniego "char" członka. – Deduplicator

+3

Typ pierwotny niekoniecznie wymaga wyrównania bajtów 'sizeof (T)'. Na przykład "podwójne" na typowych architekturach 32-bitowych to 8 bajtów, ale [często wymaga tylko wyrównania 4-bajtowego] (http://stackoverflow.com/a/11110283/706054). Ponadto wyściółka na końcu struktury tylko okładziny do wyrównania najszerszego elementu strukturalnego. Na przykład struktura 3 zmiennych char może nie mieć dopełnienia. – Matt