#pragma efekt paczki


233

Zastanawiałem się, czy ktoś mógłby mi wyjaśnić, co #pragma packrobi 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.


1
Wymusza określone wyrównanie / upakowanie struktury, ale podobnie jak wszystkie #pragmadyrektywy, są one zdefiniowane implementacyjnie.
dreamlax

A mod s = 0gdzie A to adres, a s to rozmiar typu danych; sprawdza to, czy dane nie są wyrównane.
legends2k

Odpowiedzi:


422

#pragma packinstruuje 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:

struct Test
{
   char AA;
   int BB;
   char CC;
};

Kompilator może wybrać układ struktury w pamięci w następujący sposób:

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

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:

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

I sizeof(Test)byłoby 1 × 6 = 6.

Za #pragma pack(2)pomocą powyższej struktury będzie wyglądać następująco:

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

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:

struct Test
{
   char AA;
   char CC;
   int BB;
};

i z #pragma pack(2), struktura będzie wyglądać następująco:

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

i sizeOf(Test)byłoby 3 × 2 = 6.


76
Może warto dodać wady pakowania. (Niewyosiowane dostępy obiektów są powolne w najlepszym przypadku, ale spowoduje błędy na niektórych platformach.)
jalf

11
Wydaje się, że wspomniane „ograniczenia wydajności” wyrównania mogą w rzeczywistości być zaletą w niektórych systemach danluu.com/3c-conflict .

6
@Pacerier Nie bardzo. Ten post mówi o dość ekstremalnym wyrównaniu (wyrównaniu na granicach 4KB). Procesor oczekuje pewnych minimalnych dopasowań dla różnych typów danych, ale te wymagają, w najgorszym przypadku, wyrównania 8-bajtowego (nie licząc typów wektorów, które mogą wymagać wyrównania 16 lub 32 bajtów). Brak wyrównania na tych granicach ogólnie daje zauważalny spadek wydajności (ponieważ obciążenie może być wykonane jako dwie operacje zamiast jednej), ale typ jest albo dobrze wyrównany, albo nie. Bardziej rygorystyczne wyrównanie niż to nic nie
kosztuje

6
Innymi słowy, double oczekuje na granicy 8 bajtów. Ustawienie granicy 7 bajtów zaszkodzi wydajności. Ale umieszczenie go na granicy 16, 32, 64 lub 4096 bajtów nie spowoduje, że nie przekroczysz granicy 8 bajtów. Otrzymasz taką samą wydajność procesora, a jednocześnie znacznie gorsze wykorzystanie pamięci podręcznej z powodów opisanych w tym poście.
15

4
Lekcja nie polega więc na tym, że „pakowanie jest korzystne” (pakowanie narusza naturalne wyrównanie typów, co szkodzi wydajności), ale po prostu „nie nadmiernie wyrównuj poza to, co jest wymagane”
czerwiec

27

#pragmasł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 packsł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.


17
Aby cofnąć później, wykonaj następujące czynności: #pragma pack (push, 1) i #pragma pack (pop)
malhal

16

Informuje kompilator o granicy, do której mają zostać wyrównane obiekty w strukturze. Na przykład, jeśli mam coś takiego:

struct foo { 
    char a;
    int b;
};

Z typowym komputerze 32-bitowym, wy mieliście normalnie „chce” mieć 3 bajtów pomiędzy wyściółką ai btak, że bwylą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 elementami unusedNlub ignoreN, lub coś w tym zamówienie).


„normalnie„ chciałbyś ”mieć 3 bajty wypełnienia między a i b, aby b wylądował na granicy 4 bajtów, aby zmaksymalizować prędkość dostępu” - w jaki sposób posiadanie 3 bajtów wypełnienia zmaksymalizuje prędkość dostępu?
Ashwin

8
@Ashwin: Umieszczenie bna 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.
Jerry Coffin

... jeśli spojrzysz na kod asemblera do odczytu wyrównanego i niepoprawnego int, wyrównane czytanie jest zwykle pojedynczym mnemonikiem. Odczyt nieprzystosowany może być łatwy do 10 wierszy złożenia, ponieważ składa on liczbę całkowitą, wybierając bajt po bajcie i umieszczając we właściwych miejscach rejestru.
SF.

2
@SF .: Może być - ale nawet jeśli nie jest, nie daj się zwieść - na procesorze x86 (dla jednego oczywistego przykładu) operacje są wykonywane sprzętowo, ale nadal otrzymujesz mniej więcej ten sam zestaw operacji i spowolnienie.
Jerry Coffin

8

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.

#pragmapozwala 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.


W rzeczywistości 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 a charjest zawsze prostą maską fetch-shift-maską, natomiast dostęp do intmoże być fetch-fetch-merge lub po prostu fetch, w zależności od tego, czy jest wyrównany, czy nie. intma (na x86) 32-bit (4 bajty) dostosowania bo inaczej można dostać (powiedzmy) pół intw jednej DWORD, a połowę w drugiej, a to zajęłoby dwa wyszukiwań.
Tim Čas,

3

Kompilator może wyrównywać elementy w strukturach, aby osiągnąć maksymalną wydajność na określonej platformie. #pragma packDyrektywa 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 1aby wykluczyć niechciane wyrównanie.


2

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.


1

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ą.


Właściwie używam go dość często, aby zaoszczędzić miejsce w dużych tabelach, do których często nie ma dostępu. Tam jest to tylko oszczędność miejsca, a nie ścisłe dopasowanie. (Właśnie zagłosowałem, btw. Ktoś dał ci głos negatywny.)
Todd Lehman

1

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).


1

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ę.


0

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:

#include <stdio.h>

struct a {
    char one;
    char two[2];
    char eight[8];
    char four[4];
};

struct b { 
    char one;
    short two;
    long int eight;
    int four;
};

int main(int argc, char** argv) {
    struct a twoa[2] = {}; 
    struct b twob[2] = {}; 
    printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b));
    printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob));
}

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.


Nie, to źle. Jeśli spojrzysz na pozycję w pamięci b.two, to nie jest to jeden bajt po b.one (kompilator może (i często będzie) wyrównywał b.two, więc dostosowuje się do dostępu do słów). Dla a.two to dokładnie jeden bajt po jednym. Jeśli potrzebujesz uzyskać dostęp do a.two jako krótka int, powinieneś mieć 2 alternatywy, albo użyj unii (ale zwykle nie udaje się, jeśli masz problem z endianingiem), albo rozpakuj / przekonwertuj kodem (używając odpowiedniej funkcji ntohX)
xryl669

1
sizeofzwraca a, size_tktóry należy wydrukować za pomocą%zu . Użycie niewłaściwego specyfikatora formatu wywołuje niezdefiniowane zachowanie
phuclv

0

Dlaczego chce się go używać?

Aby zmniejszyć pamięć struktury

Dlaczego nie należy go używać?

  1. Może to prowadzić do obniżenia wydajności, ponieważ niektóre systemy działają lepiej na wyrównanych danych
  2. Niektóre maszyny nie mogą odczytać niezaangażowanych danych
  3. Kod nie jest przenośny
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.