Gdy programowanie w CI okazało się nieocenione pakowanie struktur za pomocą GCC __attribute__((__packed__))
[...]
Ponieważ wspomniałeś __attribute__((__packed__))
, zakładam, że twoim celem jest wyeliminowanie całego dopełniania w obrębie struct
(aby każdy element miał wyrównanie 1-bajtowe).
Czy nie ma żadnego standardu pakowania struktur, który działałby we wszystkich kompilatorach języka C?
... a odpowiedź brzmi „nie”. Ważnym powodem jest wypełnienie i wyrównanie danych w stosunku do struktury (i ciągłe tablice struktur w stosie lub stercie). Na wielu komputerach niewyrównany dostęp do pamięci może prowadzić do potencjalnie znacznego obniżenia wydajności (choć staje się mniejszy na niektórych nowszych urządzeniach). W niektórych rzadkich przypadkach nieprawidłowy dostęp do pamięci prowadzi do błędu magistrali, którego nie można naprawić (może nawet spowodować awarię całego systemu operacyjnego).
Ponieważ standard C koncentruje się na przenośności, nie ma sensu mieć standardowego sposobu na wyeliminowanie wszystkich wypełnień w strukturze i po prostu zezwala na wyrównanie dowolnych pól, ponieważ może to spowodować ryzyko, że kod C nie będzie przenośny.
Najbezpieczniejszym i najbardziej przenośnym sposobem na wyprowadzenie takich danych do zewnętrznego źródła w sposób, który eliminuje wszelkie wypełnianie, jest serializacja do / ze strumieni bajtów zamiast tylko próby przesłania surowej zawartości pamięci structs
. Zapobiegnie to również pogorszeniu wydajności programu poza tym kontekstem serializacji, a także pozwoli na swobodne dodawanie nowych pól struct
bez wyrzucania i zakłócania działania całego oprogramowania. Daje ci również trochę miejsca na walkę z endianizmem i takimi rzeczami, jeśli kiedykolwiek stanie się to problemem.
Jest jeden sposób na wyeliminowanie wszystkich dopełnień bez sięgania po dyrektywy specyficzne dla kompilatora, chociaż ma to zastosowanie tylko wtedy, gdy względna kolejność między polami nie ma znaczenia. Biorąc pod uwagę coś takiego:
struct Foo
{
double x; // assume 8-byte alignment
char y; // assume 1-byte alignment
// 7 bytes of padding for first field
};
... potrzebujemy dopełnienia dla wyrównanego dostępu do pamięci w stosunku do adresu struktury zawierającej te pola, tak jak poniżej:
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______y.......x_______y.......x_______y.......x_______y.......
... gdzie .
wskazuje wypełnienie. Każdy x
musi wyrównać się do 8-bajtowej granicy wydajności (a czasem nawet prawidłowego zachowania).
Możesz wyeliminować dopełnienie w przenośny sposób, używając takiej reprezentacji SoA (struktura tablicy) (załóżmy, że potrzebujemy 8 Foo
instancji):
struct Foos
{
double x[8];
char y[8];
};
Skutecznie zburzyliśmy konstrukcję. W takim przypadku reprezentacja pamięci wygląda następująco:
0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______x_______x_______x_______x_______x_______x_______x_______
... i to:
01234567
yyyyyyyy
... koniec z dopełnianiem i bez angażowania nierównomiernego dostępu do pamięci, ponieważ nie uzyskujemy już dostępu do tych pól danych jako przesunięcie adresu struktury, ale zamiast tego jako przesunięcie adresu podstawowego dla tego, co faktycznie jest tablicą.
Ma to również tę zaletę, że jest szybsze w dostępie sekwencyjnym w wyniku zarówno mniejszej ilości danych do zużywania (koniec zbędnego uzupełniania w miksie, aby spowolnić odpowiednie zużycie danych przez maszynę), a także potencjał kompilatora wektoryzacji przetwarzania w bardzo trywialny sposób .
Minusem jest to, że jest to kod PITA. Jest również potencjalnie mniej wydajny w przypadku losowego dostępu, z większym krokiem między polami, gdzie często przedstawiciele AoS lub AoSoA radzą sobie lepiej. Ale to jeden ze standardowych sposobów na wyeliminowanie wyściółki i spakowanie rzeczy tak ciasno, jak to możliwe bez wkręcania z wyrównaniem wszystkiego.