Zastanawiałem się, czy ktoś mógłby mi wyjaśnić, co #pragma pack
robi oświadczenie preprocesora, a co ważniejsze, dlaczego ktoś chciałby go użyć.
Sprawdziłem stronę MSDN , która oferowała pewien wgląd, ale miałem nadzieję usłyszeć więcej od osób z doświadczeniem. Widziałem to już wcześniej w kodzie, choć wydaje mi się, że już nie mogę znaleźć gdzie.
c
c-preprocessor
pragma-pack
Cenoc
źródło
źródło
#pragma
dyrektywy, są one zdefiniowane implementacyjnie.A mod s = 0
gdzie A to adres, a s to rozmiar typu danych; sprawdza to, czy dane nie są wyrównane.Odpowiedzi:
#pragma pack
instruuje kompilator, aby spakował elementy struktury ze szczególnym wyrównaniem. Większość kompilatorów, gdy deklarujesz strukturę, wstawi dopełnianie między elementami, aby upewnić się, że są one wyrównane do odpowiednich adresów w pamięci (zwykle wielokrotność wielkości typu). Pozwala to uniknąć ograniczenia wydajności (lub całkowitego błędu) w niektórych architekturach związanych z dostępem do zmiennych, które nie są odpowiednio wyrównane. Na przykład podane 4-bajtowe liczby całkowite i następująca struktura:Kompilator może wybrać układ struktury w pamięci w następujący sposób:
i
sizeof(Test)
byłoby 4 × 3 = 12, mimo że zawiera tylko 6 bajtów danych. Najczęstszym przypadkiem użycia#pragma
(według mojej wiedzy) jest praca z urządzeniami sprzętowymi, w których należy upewnić się, że kompilator nie wstawia dopełniania do danych, a każdy element jest zgodny z poprzednim. Za#pragma pack(1)
pomocą powyższej struktury będzie wyglądać następująco:I
sizeof(Test)
byłoby 1 × 6 = 6.Za
#pragma pack(2)
pomocą powyższej struktury będzie wyglądać następująco:I
sizeof(Test)
byłoby 2 × 4 = 8.Ważna jest również kolejność zmiennych w struct. Ze zmiennymi uporządkowanymi w następujący sposób:
i z
#pragma pack(2)
, struktura będzie wyglądać następująco:i
sizeOf(Test)
byłoby 3 × 2 = 6.źródło
#pragma
służy do wysyłania do kompilatora komunikatów nieprzenośnych (jak tylko w tym kompilatorze). Rzeczy takie jak wyłączenie niektórych ostrzeżeń i struktur pakowania są częstymi przyczynami. Wyłączenie określonych ostrzeżeń jest szczególnie przydatne, jeśli kompilujesz je z ostrzeżeniami, gdy flaga błędów jest włączona.#pragma pack
służy konkretnie do wskazania, że pakowana struktura nie powinna mieć wyrównanych elementów. Jest to przydatne, gdy masz interfejs odwzorowany w pamięci na element sprzętowy i musisz być w stanie kontrolować dokładnie, gdzie wskazują różni członkowie struktury. Optymalizacja prędkości nie jest szczególnie dobra, ponieważ większość maszyn radzi sobie znacznie szybciej z wyrównanymi danymi.źródło
Informuje kompilator o granicy, do której mają zostać wyrównane obiekty w strukturze. Na przykład, jeśli mam coś takiego:
Z typowym komputerze 32-bitowym, wy mieliście normalnie „chce” mieć 3 bajtów pomiędzy wyściółką
a
ib
tak, żeb
wyląduje na granicy 4 bajtów, aby zmaksymalizować swoją szybkość dostępu (i to, co zazwyczaj dzieje się to domyślnie).Jeśli jednak musisz dopasować zewnętrznie zdefiniowaną strukturę, musisz upewnić się, że kompilator rozłoży Twoją strukturę dokładnie zgodnie z tą definicją zewnętrzną. W takim przypadku możesz dać kompilatorowi polecenie,
#pragma pack(1)
aby nie wstawiał żadnego dopełnienia między elementami - jeśli definicja struktury obejmuje dopełnianie między elementami, wstawiasz go jawnie (np. Zwykle z nazwanymi elementamiunusedN
lubignoreN
, lub coś w tym zamówienie).źródło
b
na granicy 4-bajtowej oznacza, że procesor może ją załadować, wysyłając pojedyncze obciążenie 4-bajtowe. Chociaż zależy to nieco od procesora, jeśli jest na dziwnej granicy, istnieje duża szansa, że załadowanie go będzie wymagało wydania przez procesor dwóch oddzielnych instrukcji ładowania, a następnie użyj dźwigni zmiany biegów, aby połączyć te elementy. Typowa kara to 3x wolniejsze ładowanie tego przedmiotu.Elementy danych (np. Członkowie klas i struktur) są zazwyczaj wyrównane do granic WORD lub DWORD dla procesorów obecnej generacji w celu poprawy czasów dostępu. Pobieranie DWORD pod adresem, który nie jest podzielny przez 4, wymaga co najmniej jednego dodatkowego cyklu procesora w 32-bitowym procesorze. Tak więc, jeśli masz np. Trzech członków char
char a, b, c;
, faktycznie zajmują one 6 lub 12 bajtów pamięci.#pragma
pozwala to zmienić, aby uzyskać bardziej wydajne wykorzystanie przestrzeni kosztem szybkości dostępu lub spójność przechowywanych danych między różnymi celami kompilatora. Miałem dużo zabawy z tym przejściem z kodu 16-bitowego na 32-bitowy; Oczekuję, że przeniesienie do 64-bitowego kodu spowoduje takie same bóle głowy dla niektórych kodów.źródło
char a,b,c;
zajmuje zwykle 3 lub 4 bajty (przynajmniej na x86) - ponieważ ich wymaganie wyrównania wynosi 1 bajt. Jeśli nie, to jak byś sobie poradziłchar str[] = "foo";
? Dostęp do achar
jest zawsze prostą maską fetch-shift-maską, natomiast dostęp doint
może być fetch-fetch-merge lub po prostu fetch, w zależności od tego, czy jest wyrównany, czy nie.int
ma (na x86) 32-bit (4 bajty) dostosowania bo inaczej można dostać (powiedzmy) półint
w jednejDWORD
, a połowę w drugiej, a to zajęłoby dwa wyszukiwań.Kompilator może wyrównywać elementy w strukturach, aby osiągnąć maksymalną wydajność na określonej platformie.
#pragma pack
Dyrektywa pozwala kontrolować to wyrównanie. Zwykle należy pozostawić to ustawienie domyślne, aby uzyskać optymalną wydajność. Jeśli musisz przekazać strukturę do zdalnego komputera, zwykle użyjesz go,#pragma pack 1
aby wykluczyć niechciane wyrównanie.źródło
Kompilator może umieszczać elementy struktury na określonych granicach bajtów ze względu na wydajność w określonej architekturze. Może to pozostawić niewykorzystane wypełnienie między członkami. Pakowanie struktury zmusza członków do ciągłości.
Może to być ważne na przykład, jeśli wymagana jest struktura zgodna z określonym formatem pliku lub komunikacji, w której dane muszą znajdować się w określonych miejscach w sekwencji. Jednak takie użycie nie rozwiązuje problemów endianizmu, więc chociaż jest używane, może nie być przenośne.
Może także dokładnie nakładać się na wewnętrzną strukturę rejestru niektórych urządzeń I / O, takich jak na przykład UART lub kontroler USB, aby dostęp do rejestru odbywał się za pośrednictwem struktury, a nie adresów bezpośrednich.
źródło
Prawdopodobnie zechcesz tego użyć tylko wtedy, gdy kodujesz na jakimś sprzęcie (np. Urządzeniu zamapowanym w pamięci), który ma ścisłe wymagania dotyczące porządkowania i wyrównywania rejestrów.
Jednak wygląda to na dość tępe narzędzie do osiągnięcia tego celu. Lepszym rozwiązaniem byłoby zakodowanie mini-sterownika w asemblerze i nadanie mu interfejsu wywołującego w języku C, zamiast manipulowania tą pragmą.
źródło
Używałem go wcześniej w kodzie, ale tylko do połączenia ze starszym kodem. To była aplikacja Cocoa Mac OS X, która musiała ładować pliki preferencji z wcześniejszej wersji Carbon (która sama była kompatybilna wstecz z oryginalną wersją M68k System 6.5 ... masz pomysł). Pliki preferencji w oryginalnej wersji były zrzutem binarnym struktury konfiguracji, która wykorzystywała,
#pragma pack(1)
aby uniknąć zajmowania dodatkowego miejsca i oszczędzania śmieci (tj. Bajtów wypełniania, które w przeciwnym razie byłyby w strukturze).Oryginalni autorzy kodu używali również
#pragma pack(1)
do przechowywania struktur, które były używane jako komunikaty w komunikacji między procesami. Myślę, że powodem tego było uniknięcie możliwości nieznanego lub zmienionego rozmiaru dopełnienia, ponieważ kod czasami sprawdzał określoną część struktury komunikatu, licząc od początku liczbę bajtów (ewww).źródło
Widziałem, jak ludzie używają go, aby upewnić się, że struktura zajmuje całą linię pamięci podręcznej, aby zapobiec fałszywemu udostępnianiu w kontekście wielowątkowym. Jeśli będziesz mieć dużą liczbę obiektów, które będą domyślnie luźno spakowane, może to zaoszczędzić pamięć i poprawić wydajność pamięci podręcznej, aby spakować je ściślej, chociaż nieprzydzielony dostęp do pamięci zwykle spowalnia rzeczy, więc może to mieć wadę.
źródło
Zauważ, że istnieją inne sposoby osiągnięcia spójności danych, które oferuje pakiet #pragma (na przykład niektóre osoby używają pakietu #pragma (1) do struktur, które powinny być przesyłane przez sieć). Na przykład zobacz następujący kod i jego kolejne dane wyjściowe:
Dane wyjściowe są następujące: sizeof (struct a): 15, sizeof (struct b): 24 sizeof (twoa): 30, sizeof (twob): 48
Zauważ, że rozmiar struct a jest dokładnie tym, czym jest liczba bajtów, ale w struct b dodano dopełnienie (zobacz to, aby uzyskać szczegółowe informacje na temat dopełniania). Robiąc to, w przeciwieństwie do pakietu #pragma, możesz mieć kontrolę nad konwersją „formatu drutu” na odpowiednie typy. Na przykład „char two [2]” w „short int” i tak dalej.
źródło
sizeof
zwraca a,size_t
który należy wydrukować za pomocą%zu
. Użycie niewłaściwego specyfikatora formatu wywołuje niezdefiniowane zachowanieDlaczego chce się go używać?
Aby zmniejszyć pamięć struktury
Dlaczego nie należy go używać?
źródło