Dlaczego sizeof dla struktury nie jest równy sumie sizeof każdego członka?

698

Dlaczego sizeofoperator zwraca rozmiar struktury większy niż całkowite rozmiary elementów konstrukcji?

Kevin
źródło
14
Zobacz C FAQ na temat dostosowania pamięci. c-faq.com/struct/align.esr.html
Richard Chambers
48
Anegdota: pojawił się prawdziwy wirus komputerowy, który umieścił swój kod w strukturze strukturalnej w programie hosta.
Elazar
4
@Elazar To imponujące! Nigdy bym nie pomyślał, że można do tak małych obszarów wykorzystać wszystko. Czy możesz podać więcej szczegółów?
OmarL
1
@Wilson - jestem pewien, że dotyczyło to wielu plików jmp.
hoodaticus
4
Zobacz wypełnienie struktury , pakowanie : The Lost Art of C Structure Packing Eric S. Raymond
EsmaeelE

Odpowiedzi:

649

Wynika to z dodania wypełnienia w celu spełnienia ograniczeń wyrównania. Wyrównanie struktury danych wpływa zarówno na wydajność, jak i poprawność programów:

  • Niewłaściwy dostęp może być poważnym błędem (często SIGBUS).
  • Niewłaściwie wyrównany dostęp może być miękkim błędem.
    • Albo poprawione sprzętowo, aby uzyskać niewielki spadek wydajności.
    • Lub poprawione przez emulację w oprogramowaniu w celu poważnego obniżenia wydajności.
    • Ponadto atomowość i inne gwarancje współbieżności mogą zostać zerwane, co prowadzi do subtelnych błędów.

Oto przykład z typowymi ustawieniami procesora x86 (wszystkie używane tryby 32- i 64-bitowe):

struct X
{
    short s; /* 2 bytes */
             /* 2 padding bytes */
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 3 padding bytes */
};

struct Y
{
    int   i; /* 4 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
    short s; /* 2 bytes */
};

struct Z
{
    int   i; /* 4 bytes */
    short s; /* 2 bytes */
    char  c; /* 1 byte */
             /* 1 padding byte */
};

const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */

Można zminimalizować rozmiar struktur, sortując elementy według wyrównania (wystarczające jest sortowanie według rozmiaru w typach podstawowych) (podobnie jak struktura Zw powyższym przykładzie).

WAŻNA UWAGA: Zarówno standardy C, jak i C ++ stwierdzają, że wyrównanie struktury jest zdefiniowane w ramach implementacji. Dlatego każdy kompilator może wybrać inne wyrównanie danych, co skutkuje odmiennymi i niekompatybilnymi układami danych. Z tego powodu, mając do czynienia z bibliotekami, które będą używane przez różne kompilatory, ważne jest, aby zrozumieć, w jaki sposób kompilatory wyrównują dane. Niektóre kompilatory mają ustawienia wiersza polecenia i / lub specjalne #pragmainstrukcje do zmiany ustawień wyrównania struktury.

Kevin
źródło
38
Chcę tutaj zanotować: większość procesorów karze cię za niewyrównany dostęp do pamięci (jak wspomniałeś), ale nie możesz zapomnieć, że wielu całkowicie go nie zezwala. W szczególności większość żetonów MIPS spowoduje wyjątek dla nieprzypisanego dostępu.
Cody Brocious,
35
Układy x86 są w rzeczywistości raczej unikalne, ponieważ umożliwiają niezaangażowany dostęp, aczkolwiek karany; AFAIK większość żetonów wyrzuci wyjątki, nie tylko kilka. PowerPC jest kolejnym częstym przykładem.
Dark Shikari,
6
Włączenie pragmatyki dla niezrównanego dostępu ogólnie powoduje, że twój kod się powiększa, na procesorach, które generują błędy niewspółosiowości, ponieważ należy wygenerować kod, który naprawi każdą niewspółosiowość. ARM generuje również błędy niewspółosiowości.
Mike Dimmick
5
@Dark - całkowicie się zgadzam. Ale większość procesorów do komputerów stacjonarnych to x86 / x64, więc większość układów nie powoduje błędów wyrównywania danych;)
Aaron
27
Nieprzypisany dostęp do danych jest zwykle funkcją występującą w architekturach CISC, a większość architektur RISC go nie obejmuje (ARM, MIPS, PowerPC, Cell). W rzeczywistości większość układów NIE jest procesorami do komputerów stacjonarnych, ponieważ są one osadzone według liczby układów, a zdecydowana większość z nich to architektury RISC.
Lara Dougan,
192

Pakowanie i wyrównanie bajtów, zgodnie z opisem w C FAQ tutaj :

To jest do wyrównania. Wiele procesorów nie ma dostępu do 2- i 4-bajtowych liczb (np. Int i long ints), jeśli są one zatłoczone we wszystkie strony.

Załóżmy, że masz taką strukturę:

struct {
    char a[3];
    short int b;
    long int c;
    char d[3];
};

Teraz możesz pomyśleć, że powinna istnieć możliwość spakowania tej struktury do pamięci w następujący sposób:

+-------+-------+-------+-------+
|           a           |   b   |
+-------+-------+-------+-------+
|   b   |           c           |
+-------+-------+-------+-------+
|   c   |           d           |
+-------+-------+-------+-------+

Ale na procesorze jest o wiele łatwiej, jeśli kompilator tak to zorganizuje:

+-------+-------+-------+
|           a           |
+-------+-------+-------+
|       b       |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           |
+-------+-------+-------+

W wersji spakowanej zwróć uwagę, że co najmniej trochę trudno jest mi zobaczyć, jak pola b i c owijają się wokół siebie? Krótko mówiąc, jest to również trudne dla procesora. Dlatego większość kompilatorów wypełnia strukturę (jakby dodatkowymi niewidocznymi polami) w następujący sposób:

+-------+-------+-------+-------+
|           a           | pad1  |
+-------+-------+-------+-------+
|       b       |     pad2      |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           | pad3  |
+-------+-------+-------+-------+
EmmEff
źródło
1
Jakie jest zastosowanie gniazd pamięci pad1, pad2 i pad3.
Lakshmi Sreekanth Chitla
7
@YoYoYonnY to niemożliwe. Kompilator nie może
zmieniać
@EmmEff może to być źle, ale nie całkiem rozumiem: dlaczego nie ma gniazda pamięci dla wskaźnika w tablicach?
Balázs Börcsök
1
@ BalázsBörcsök Są to tablice o stałej wielkości, więc ich elementy są przechowywane bezpośrednio w strukturze o ustalonych przesunięciach. Kompilator wie o tym wszystkim w czasie kompilacji, więc wskaźnik jest niejawny. Na przykład, jeśli masz zmienną struct tego typu o nazwie swtedy &s.a == &si &s.d == &s + 12(biorąc pod uwagę wyrównanie pokazane w odpowiedzi). Wskaźnik jest przechowywany tylko wtedy, gdy tablice mają zmienny rozmiar (np. aZostał zadeklarowany char a[]zamiast char a[3]), ale wtedy elementy muszą być przechowywane gdzie indziej.
kbolino
27

Jeśli chcesz, aby struktura miała określony rozmiar w GCC, na przykład użyj __attribute__((packed)).

W systemie Windows można ustawić wyrównanie na jeden bajt, gdy używany jest komparator cl.exe z opcją / Zp .

Zwykle procesorowi łatwiej jest uzyskać dostęp do danych stanowiących wielokrotność 4 (lub 8), zależnie od platformy, a także od kompilatora.

Zasadniczo jest to kwestia dostosowania.

Musisz mieć dobre powody, aby to zmienić.

INS
źródło
5
„dobre powody” Przykład: Utrzymywanie zgodności binarnej (wypełniania) między systemami 32-bitowymi i 64-bitowymi w celu uzyskania złożonej struktury w kodzie demonstracyjnym typu proof-of-concept, który zostanie zaprezentowany jutro. Czasami konieczność musi mieć pierwszeństwo przed właściwością.
Mr.Ree,
2
Wszystko jest w porządku, chyba że wspominasz o systemie operacyjnym. Jest to problem związany z szybkością procesora, system operacyjny w ogóle nie jest zaangażowany.
Blaisorblade,
3
Innym dobrym powodem jest umieszczanie strumienia danych w strukturze, np. Podczas analizowania protokołów sieciowych.
ceo
1
@dolmen Właśnie zauważyłem, że „systemowi Operatin łatwiej jest uzyskać dostęp do danych” jest nieprawidłowy, ponieważ system operacyjny nie ma dostępu do danych.
Blaisorblade
1
@dolmen W rzeczywistości należy mówić o ABI (binarnym interfejsie aplikacji). Domyślne wyrównanie (używane, jeśli nie zmienisz go w źródle) zależy od ABI, a wiele systemów operacyjnych obsługuje wiele ABI (powiedzmy 32- i 64-bitowy lub dla plików binarnych z różnych systemów operacyjnych lub dla różnych sposobów kompilacji te same pliki binarne dla tego samego systemu operacyjnego). OTOH, to, jakie wyrównanie jest wygodne pod względem wydajności, zależy od procesora - pamięć jest dostępna w ten sam sposób, niezależnie od tego, czy używasz trybu 32- lub 64-bitowego (nie mogę komentować trybu rzeczywistego, ale w dzisiejszych czasach wydaje się mało istotny dla wydajności). IIRC Pentium zaczął preferować wyrównanie do 8 bajtów.
Blaisorblade
15

Może to być spowodowane wyrównaniem bajtów i dopełnianiem, dzięki czemu struktura wychodzi na parzystą liczbę bajtów (lub słów) na twojej platformie. Na przykład w C w systemie Linux następujące 3 struktury:

#include "stdio.h"


struct oneInt {
  int x;
};

struct twoInts {
  int x;
  int y;
};

struct someBits {
  int x:2;
  int y:6;
};


int main (int argc, char** argv) {
  printf("oneInt=%zu\n",sizeof(struct oneInt));
  printf("twoInts=%zu\n",sizeof(struct twoInts));
  printf("someBits=%zu\n",sizeof(struct someBits));
  return 0;
}

Członkowie, których rozmiary (w bajtach) wynoszą odpowiednio 4 bajty (32 bity), 8 bajtów (2x 32 bity) i 1 bajt (2 + 6 bitów). Powyższy program (w systemie Linux za pomocą gcc) drukuje rozmiary jako 4, 8 i 4 - w których ostatnia struktura jest wypełniona, tak że jest to pojedyncze słowo (4 x 8 bitów na mojej 32-bitowej platformie).

oneInt=4
twoInts=8
someBits=4
Kyle Burton
źródło
4
„C w systemie Linux za pomocą gcc” nie wystarcza do opisania twojej platformy. Wyrównanie zależy głównie od architektury procesora.
dolmen
- @ Kyle Burton. Przepraszam, nie rozumiem, dlaczego rozmiar struktury „someBits” jest równy 4, oczekuję 8 bajtów, ponieważ zadeklarowano 2 liczby całkowite (2 * sizeof (int)) = 8 bajtów. dzięki
youpilat13
1
Cześć @ youpilat13, :2i :6faktycznie określają 2 i 6 bitów, a nie pełne 32-bitowe liczby całkowite w tym przypadku. someBits.x, będąc tylko 2 bitami, może przechowywać tylko 4 możliwe wartości: 00, 01, 10 i 11 (1, 2, 3 i 4). Czy to ma sens? Oto artykuł na temat funkcji: geeksforgeeks.org/bit-fields-c
Kyle Burton
11

Zobacz też:

dla Microsoft Visual C:

http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx

i zgodność deklaracji GCC z kompilatorem Microsoft:

http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html

Oprócz poprzednich odpowiedzi należy pamiętać, że niezależnie od opakowania, nie ma gwarancji członkostwa w C ++ . Kompilatory mogą (i na pewno tak robią) dodawać wirtualny wskaźnik tabeli i elementy struktur podstawowych do struktury. Standard nie zapewnia nawet istnienia wirtualnej tabeli (implementacja mechanizmu wirtualnego nie jest określona) i dlatego można stwierdzić, że taka gwarancja jest po prostu niemożliwa.

Jestem całkiem pewien, że kolejność członków jest gwarantowana w C , ale nie liczyłbym na to, pisząc program międzyplatformowy lub kompilator.

lkanab
źródło
4
„Jestem pewien, że kolejność członków jest mruknięta w C”. Tak, C99 mówi: „W obrębie obiektu struktury elementy niebędące polami bitowymi i jednostki, w których rezydują pola bitowe, mają adresy, które zwiększają się w kolejności, w której są deklarowane”. Bardziej standardowa dobroć na: stackoverflow.com/a/37032302/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
8

Rozmiar struktury jest większy niż suma jej części z powodu tak zwanego upakowania. Określony procesor ma preferowany rozmiar danych, z którym współpracuje. Preferowany rozmiar większości nowoczesnych procesorów to 32 bity (4 bajty). Dostęp do pamięci, gdy dane znajdują się na tego rodzaju granicy, jest bardziej wydajny niż rzeczy, które przekraczają granicę tego rozmiaru.

Na przykład. Rozważ prostą strukturę:

struct myStruct
{
   int a;
   char b;
   int c;
} data;

Jeśli maszyna jest maszyną 32-bitową, a dane są wyrównane na 32-bitowej granicy, widzimy bezpośredni problem (zakładając brak wyrównania struktury). W tym przykładzie załóżmy, że dane struktury zaczynają się od adresu 1024 (0x400 - zauważ, że najniższe 2 bity to zero, więc dane są wyrównane do 32-bitowej granicy). Dostęp do danych. A będzie działał dobrze, ponieważ zaczyna się na granicy - 0x400. Dostęp do data.b również będzie działał dobrze, ponieważ ma on adres 0x404 - kolejna 32-bitowa granica. Ale niezaangażowana struktura umieściłaby data.c pod adresem 0x405. 4 bajty danych. C są w 0x405, 0x406, 0x407, 0x408. Na maszynie 32-bitowej system odczytuje dane. C podczas jednego cyklu pamięci, ale otrzymuje tylko 3 z 4 bajtów (4 bajt znajduje się na następnej granicy). Tak więc system musiałby wykonać drugi dostęp do pamięci, aby uzyskać 4. bajt,

Teraz, jeśli zamiast wstawić data.c pod adresem 0x405, kompilator dopełnił strukturę o 3 bajty i umieścił data.c pod adresem 0x408, wówczas system potrzebowałby tylko 1 cyklu na odczyt danych, skracając czas dostępu do tego elementu danych o 50%. Padding zamienia wydajność pamięci na wydajność przetwarzania. Biorąc pod uwagę, że komputery mogą mieć ogromne ilości pamięci (wiele gigabajtów), kompilatory uważają, że zamiana (prędkość ponad rozmiar) jest rozsądna.

Niestety ten problem staje się zabójczy, gdy próbujesz wysyłać struktury przez sieć lub nawet zapisywać dane binarne do pliku binarnego. Wypełnienie wstawiane między elementy struktury lub klasy może zakłócać przesyłanie danych do pliku lub sieci. Aby napisać kod przenośny (taki, który trafi do kilku różnych kompilatorów), prawdopodobnie będziesz musiał uzyskać dostęp do każdego elementu struktury osobno, aby zapewnić właściwe „pakowanie”.

Z drugiej strony różne kompilatory mają różne możliwości zarządzania pakowaniem struktury danych. Na przykład w Visual C / C ++ kompilator obsługuje komendę #pragma pack. Umożliwi to dostosowanie pakowania i wyrównania danych.

Na przykład:

#pragma pack 1
struct MyStruct
{
    int a;
    char b;
    int c;
    short d;
} myData;

I = sizeof(myData);

Powinienem mieć teraz długość 11. Bez pragmy, mógłbym mieć wszystko od 11 do 14 (a dla niektórych systemów aż do 32), w zależności od domyślnego pakowania kompilatora.

sid1138
źródło
Omawia to konsekwencje wypełnienia konstrukcji, ale nie odpowiada na pytanie.
Keith Thompson,
... z powodu tego, co nazywa się pakowaniem. ... - Myślę, że masz na myśli„ wypełnianie ”.„ Preferowany rozmiar większości nowoczesnych procesorów, jeśli 32-bitowy (4 bajty) ”- To trochę nadmierne uproszczenie. Zazwyczaj rozmiarach 8, 16, obsługiwane są 32 i 64 bity, często każdy rozmiar ma swoje własne ustawienie i nie jestem pewien, odpowiedź dodaje wszelkie nowe informacje, które nie znajduje się już w przyjętym odpowiedź..
Keith Thompson
1
Kiedy mówiłem „pakowanie”, miałem na myśli sposób, w jaki kompilator pakuje dane do struktury (i może to zrobić, wypełniając małe elementy, ale nie musi wypełniać, ale zawsze się pakuje). Jeśli chodzi o rozmiar - mówiłem o architekturze systemu, a nie o tym, co system będzie obsługiwał dostęp do danych (co różni się znacznie od podstawowej architektury magistrali). Jeśli chodzi o twój ostatni komentarz, podałem uproszczone i rozszerzone wyjaśnienie jednego aspektu kompromisu (szybkość w porównaniu z rozmiarem) - głównego problemu programistycznego. Opisuję również sposób rozwiązania problemu - tego nie było w zaakceptowanej odpowiedzi.
sid1138
„Pakowanie” w tym kontekście zwykle odnosi się do przydzielania członków bardziej niż domyślnie, jak w przypadku #pragma pack. Jeśli członkowie zostaną przydzieleni w ramach domyślnego wyrównania, ogólnie powiedziałbym, że struktura nie jest zapakowana.
Keith Thompson,
Pakowanie to rodzaj przeciążonego terminu. Oznacza to, w jaki sposób umieszczasz elementy struktury w pamięci. Podobne do znaczenia wkładania przedmiotów do pudełka (pakowanie do przenoszenia). Oznacza to także zapisywanie elementów w pamięci bez wyściółki (rodzaj krótkiej ręki dla „ciasno upakowanego”). Następnie jest wersja polecenia tego słowa w poleceniu #pragma pack.
sid1138
5

Może to zrobić, jeśli domyślnie lub jawnie ustawiłeś wyrównanie struktury. Strukturę, która jest wyrównana 4, zawsze będzie wielokrotnością 4 bajtów, nawet jeśli rozmiar jej elementów byłby czymś, co nie jest wielokrotnością 4 bajtów.

Również biblioteka może być skompilowana pod x86 z 32-bitowymi intami i możesz porównywać jej komponenty w procesie 64-bitowym, dałbyś inny wynik, gdybyś robił to ręcznie.

Orion Adrian
źródło
5

Wersja standardowa C99 N1256

http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf

6.5.3.4 Wielkość operatora :

3 Po zastosowaniu do operandu, który ma strukturę lub typ unii, wynikiem jest całkowita liczba bajtów w takim obiekcie, w tym dopełnienie wewnętrzne i końcowe.

6.7.2.1 Specyfikacje struktury i związków :

13 ... W obiekcie struktury może znajdować się nienazwane wypełnienie, ale nie na jego początku.

i:

15 Na końcu konstrukcji lub połączenia może znajdować się nienazwane wypełnienie.

Nowa funkcja elastycznego elementu tablicy C99 ( struct S {int is[];};) może również wpływać na dopełnianie:

16 W szczególnym przypadku ostatni element struktury z więcej niż jednym nazwanym elementem może mieć niepełny typ tablicy; nazywa się to elastycznym elementem tablicy. W większości sytuacji elastyczny element tablicy jest ignorowany. W szczególności rozmiar struktury jest taki, jakby elastyczny element matrycowy został pominięty, z wyjątkiem tego, że może on mieć więcej wypełniania końcowego, niż wynikałoby z pominięcia.

Załącznik J Zagadnienia dotyczące przenośności przypomina:

Następujące nie są określone: ​​...

  • Wartość bajtów dopełniania podczas przechowywania wartości w strukturach lub związkach (6.2.6.1)

Wersja standardowa C ++ 11 N3337

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

5.3.3 Rozmiar :

2 Po zastosowaniu do klasy wynikiem jest liczba bajtów w obiekcie tej klasy, w tym wszelkie wypełnienia wymagane do umieszczenia obiektów tego typu w tablicy.

9.2 Członkowie klasy :

Wskaźnik do obiektu struktury o standardowym układzie, odpowiednio przekonwertowany za pomocą reinterpret_cast, wskazuje na jego początkowy element członkowski (lub jeśli ten element jest polem bitowym, a następnie na jednostkę, w której się znajduje) i odwrotnie. [Uwaga: W związku z tym może być nienazwane wypełnienie w obiekcie struktury o standardowym układzie, ale nie na jego początku, co jest konieczne do osiągnięcia odpowiedniego wyrównania. - uwaga końcowa]

Znam wystarczająco dużo C ++, aby zrozumieć notatkę :-)

Ciro Santilli
źródło
4

Oprócz innych odpowiedzi, struct może (ale zwykle nie) ma funkcji wirtualnych, w którym to przypadku rozmiar struktury będzie również zawierał przestrzeń dla vtbl.

JohnMcG
źródło
8
Nie do końca. W typowych implementacjach do struktury dodaje się wskaźnik vtable .
Don Wakefield
3

Język C pozostawia kompilatorowi pewną swobodę w zakresie lokalizacji elementów strukturalnych w pamięci:

  • dziury pamięci mogą pojawić się między dowolnymi dwoma komponentami i po ostatnim komponencie. Wynika to z faktu, że niektóre typy obiektów na komputerze docelowym mogą być ograniczone przez granice adresowania
  • Rozmiar „otworów pamięci” zawarty w wyniku sizeof operatora. Sizeof nie obejmuje tylko rozmiaru elastycznej tablicy, która jest dostępna w C / C ++
  • Niektóre implementacje języka pozwalają kontrolować układ struktur za pomocą opcji pragma i kompilatora

Język C zapewnia programistom pewne zapewnienie układu elementów w strukturze:

  • kompilatory wymagane do przypisania sekwencji komponentów zwiększających adresy pamięci
  • Adres pierwszego komponentu pokrywa się z adresem początkowym struktury
  • nienazwane pola bitowe mogą być zawarte w strukturze do wymaganego wyrównania adresu sąsiednich elementów

Problemy związane z wyrównaniem elementów:

  • Różne komputery na różne sposoby ustawiają krawędzie obiektów
  • Różne ograniczenia szerokości pola bitowego
  • Komputery różnią się sposobem przechowywania bajtów jednym słowem (Intel 80x86 i Motorola 68000)

Jak działa wyrównanie:

  • Objętość zajmowana przez strukturę jest obliczana jako rozmiar wyrównanego pojedynczego elementu tablicy takich struktur. Struktura powinna zakończyć się, aby pierwszy element następnej następnej struktury nie naruszał wymagań wyrównania

ps Bardziej szczegółowe informacje są dostępne tutaj: „Samuel P.Harbison, Guy L.Steele CA Reference, (5.6.2 - 5.6.7)”

bruziuz
źródło
2

Chodzi o to, że ze względu na szybkość i pamięć podręczną operandy powinny być odczytywane z adresów dopasowanych do ich naturalnego rozmiaru. Aby tak się stało, kompilator wstawia elementy struktury, tak aby następujący element lub następna struktura zostały wyrównane.

struct pixel {
    unsigned char red;   // 0
    unsigned char green; // 1
    unsigned int alpha;  // 4 (gotta skip to an aligned offset)
    unsigned char blue;  // 8 (then skip 9 10 11)
};

// next offset: 12

Architektura x86 zawsze była w stanie pobrać źle wyrównane adresy. Jest jednak wolniejszy i gdy niewspółosiowość nakłada się na dwie różne linie pamięci podręcznej, wówczas eksmituje dwie linie pamięci podręcznej, gdy wyrównany dostęp spowoduje tylko jedną.

Niektóre architektury faktycznie muszą wychwytywać źle ustawione odczyty i zapisy, a także wczesne wersje architektury ARM (tej, która ewoluowała we wszystkie dzisiejsze mobilne procesory) ... cóż, w rzeczywistości po prostu zwróciły złe dane. (Zignorowali bity niskiego rzędu).

Na koniec zauważ, że linie pamięci podręcznej mogą być dowolnie duże, a kompilator nie próbuje zgadywać ich ani dokonywać kompromisu między prędkością a przestrzenią. Zamiast tego decyzje dotyczące wyrównania są częścią ABI i reprezentują minimalne wyrównanie, które ostatecznie równomiernie zapełni linię pamięci podręcznej.

TL; DR: wyrównanie jest ważne.

DigitalRoss
źródło