Wyrównanie pamięci: jak używać alignof / alignas?

82

Obecnie pracuję z pamięcią współdzieloną.

Nie rozumiem alignofi alignas.

cppreference jest niejasne: alignofzwraca „wyrównanie”, ale co to jest „wyrównanie”? liczba bajtów do dodania dla następnego bloku do wyrównania? wyściełany rozmiar? Przepełnienie stosu / wpisy na blogach również są niejasne.

Czy ktoś może wyjaśnić jasno alignofi alignas?

Offirmo
źródło
1
cppreference stara się być raczej odniesieniem niż samouczkiem
Cubbi
1
@Cubbi: możesz też sprawdzić na cplusplus.com, jest debata, która strona jest lepsza, dla niektórych tematów cplusplus jest lepsza, dla innych preferencji cp jest lepsza, stwierdziłem, że obie strony nie są równe
CoffeDeveloper
2
@DarioOO Ja tylko odpowiadając dlaczego cppreference nie wyjaśnia koncepcję wyrównania na alignofstronie (to robi teraz, na work-in-progress strony obiektu ). Nie wiem, jakie znaczenie ma strona cplusplus.com.
Cubbi

Odpowiedzi:

82

Wyrównanie jest ograniczeniem, na którym można zapisać pierwszy bajt wartości w pamięci. (Konieczne jest poprawienie wydajności procesorów i zezwolenie na użycie niektórych instrukcji, które działają tylko na danych z określonym wyrównaniem, na przykład SSE musi być wyrównane do 16 bajtów, a AVX do 32 bajtów).

Wyrównanie 16 oznacza, że ​​adresy pamięci będące wielokrotnością 16 są jedynymi prawidłowymi adresami.

alignas

wymuś wyrównanie do wymaganej liczby bajtów. Możesz wyrównać tylko do potęg 2: 1, 2, 4, 8, 16, 32, 64, 128, ...

#include <cstdlib>
#include <iostream>

int main() {
    alignas(16) int a[4];
    alignas(1024) int b[4];
    printf("%p\n", a);
    printf("%p", b);
}

przykładowe dane wyjściowe:

0xbfa493e0
0xbfa49000  // note how many more "zeros" now.
// binary equivalent
1011 1111 1010 0100 1001 0011 1110 0000
1011 1111 1010 0100 1001 0000 0000 0000 // every zero is just a extra power of 2

drugie słowo kluczowe

alignof

jest bardzo wygodny, nie możesz zrobić czegoś takiego

int a[4];
assert(a % 16 == 0); // check if alignment is to 16 bytes: WRONG compiler error

ale możesz to zrobić

assert(alignof(a) == 16);
assert(alignof(b) == 1024);

zauważ, że w rzeczywistości jest to bardziej rygorystyczne niż prosta operacja „%” (moduł). W rzeczywistości wiemy, że coś wyrównanego do 1024 bajtów jest koniecznie wyrównane do 1, 2, 4, 8 bajtów, ale

 assert(alignof(b) == 32); // fail.

Aby być bardziej precyzyjnym, „alignof” zwraca największą potęgę 2 do tego, co jest wyrównane.

Również alignof jest dobrym sposobem, aby z góry poznać minimalne wymagania dotyczące dopasowania dla podstawowych typów danych (prawdopodobnie zwróci 1 dla znaków, 4 dla typu float itp.).

Nadal legalne:

alignas(alignof(float)) float SqDistance;

Coś z wyrównaniem 16 zostanie następnie umieszczone na następnym dostępnym adresie, który jest wielokrotnością 16 (może być niejawne wypełnienie od ostatnio używanego adresu).

CoffeDeveloper
źródło
10
W przeciwieństwie do sizeof, alignofmożna zastosować tylko do pliku type-id.
neverhoodboy
jest alignof()(i odpowiednik alignas()) oceniany w czasie kompilacji, więc nie ma narzutu w czasie wykonywania?
nonsensation
Nie. Nie jest to możliwe, kompilator może to zrobić jako optymalizację w bardzo niewielu przypadkach, ale generalnie nie będzie wiedział, w jaki sposób adresy pamięci są wyrównane przed oszacowaniem dwóch funkcji. Wystarczy spojrzeć na zestaw wygenerowany przez mój przykład: goo.gl/ZbemBF
CoffeDeveloper
1
@Serthy To clarify alignof to stała czasu kompilacji. alignasnie jest i będzie musiał być obsługiwany przez Twoją implementację new(wymaganie standardu) lub przez niestandardowy alokator std .
Aidiakapi
Dobra odpowiedź, ale wymaga potraktowania structi członków struktury, która jest static. alignasokazuje się znacznie bardziej wybredny niż __attribute__((aligned)), szczególnie w przypadku kompilatorów takich jak Clang.
jww
11

Wyrównanie nie jest wypełnieniem (chociaż czasami wprowadzane jest wypełnienie, aby spełnić wymagania dotyczące wyrównania). Jest to wewnętrzna właściwość typu C ++. Aby umieścić to w standardese ( 3.11[basic.align])

Typy obiektów mają wymagania wyrównania (3.9.1, 3.9.2), które nakładają ograniczenia na adresy, pod którymi można alokować obiekt tego typu. Wyrównanie to zdefiniowana w implementacji wartość całkowita reprezentująca liczbę bajtów między kolejnymi adresami, pod którymi można przydzielić dany obiekt. Typ obiektu nakłada wymóg wyrównania na każdy obiekt tego typu; ściślejsze wyrównanie można zażądać za pomocą specyfikatora wyrównania (7.6.2).

Cubbi
źródło
1
Bardzo interesujące. Czy mógłbyś podać kilka przykładów? Czy alignof (struct X) == sizeof (struct X)? Dlaczego nie ?
Offirmo
1
@Offirmo no, z wyjątkiem przypadku: struct X { char a; char b}ma rozmiar 2 i wymóg wyrównania 1, w rozsądnych systemach (może być przydzielony pod dowolnym adresem, ponieważ znak można przypisać pod dowolnym adresem)
Cubbi
wymóg wyrównania 1 ???? Och, rozumiem: myślałem, że wyrównanie zawsze odbywało się na „naturalnych” 32-bitowych / 64-bitowych granicach, ale najwyraźniej nie. To wyjaśnia wszystko ... Więc na zwykłych maszynach wynik alignof () zawsze będzie miał maksimum na 4 (32 bity) lub 8 (64 bity). Czy mam rację?
Offirmo
@Offirmo "naturalne" alignof osiągnie maksymalny poziom alignof(std::max_align_t), co jest 16na moim Linuksie (niezależnie od tego, czy kompiluję -m32 czy -m64), ale możesz zwiększyć to za pomocąalignas
Cubbi
7

Każdy typ ma wymagania dotyczące wyrównania. Ogólnie rzecz biorąc, jest to tak, aby dostęp do zmiennych tego typu był efektywny, bez konieczności powodowania, że ​​CPU generuje więcej niż jeden dostęp do odczytu / zapisu, aby dotrzeć do dowolnego elementu typu danych. Ponadto zapewnia również sprawne kopiowanie całej zmiennej. alignofzwróci wymagania dotyczące wyrównania dla danego typu.

alignasjest używany do wymuszania wyrównania dla typu danych (o ile nie jest mniej rygorystyczny, niż alignofzwracany przez ten typ danych)

levengli
źródło
3

Wyrównanie to właściwość związana z adresem pamięci. Po prostu możemy powiedzieć, że jeśli adres X jest wyrównany do Z, to x jest wielokrotnością Z, czyli X = Zn + 0. Tutaj ważne jest to, że Z jest zawsze potęgą 2.

Wyrównanie jest właściwością adresu pamięci, wyrażoną jako adres numeryczny modulo a potęga 2. Na przykład adres 0x0001103F modulo 4 to 3. Mówi się, że adres ten jest wyrównany do 4n + 3, gdzie 4 wskazuje wybraną moc 2. Wyrównanie adresu zależy od wybranej potęgi 2. Ten sam adres modulo 8 wynosi 7. Mówi się, że adres jest wyrównany do X, jeśli jego wyrównanie wynosi Xn + 0.

Powyższą instrukcję można znaleźć w odwołaniu do Microsoft C ++.

Jeżeli element danych jest przechowywany w pamięci z adresem, który jest wyrównany do jego rozmiaru, wówczas mówi się, że element danych jest wyrównany naturalnie , w przeciwnym razie źle wyrównany. Na przykład: jeśli zmienna całkowita o rozmiarze 4 bajtów jest przechowywana pod adresem, który jest wyrównany do 4, to możemy powiedzieć, że zmienna jest wyrównana naturalnie, czyli adres zmiennej powinien być wielokrotnością 4.

Kompilatory zawsze starają się unikać niedopasowań. W przypadku prostych typów danych adresy są wybierane tak, aby stanowiły wielokrotność rozmiaru zmiennej w bajtach. Kompilator również odpowiednio wypełnia w przypadku struktur w celu naturalnego wyrównania i dostępu. Tutaj struktura zostanie wyrównana do maksymalnych rozmiarów różnych elementów danych w strukturze. Np .:

    struct abc
   {
        int a;
        char b;
   };

Tutaj struktura abc jest wyrównana do 4, co jest rozmiarem elementu int, który jest oczywiście większy niż 1 bajt (rozmiar elementu char).

alignas

Ten specyfikator służy do wyrównania typów zdefiniowanych przez użytkownika, takich jak struktura, klasa itp., Do określonej wartości, która jest potęgą 2.

alignof

Jest to rodzaj operatora służącego do uzyskiwania wartości, do której jest wyrównana struktura lub typ klasy. na przykład:

#include <iostream>
struct alignas(16) Bar
{
    int i; // 4 bytes
    int n; // 4 bytes
    short s; // 2 bytes
};
int main()
{
    std::cout << alignof(Bar) << std::endl; // output: 16
}
JOSEPH BENOY
źródło