Tak, __attribute__((packed))
jest potencjalnie niebezpieczny w niektórych systemach. Symptom prawdopodobnie nie pojawi się na x86, co tylko czyni problem bardziej podstępnym; testowanie na systemach x86 nie ujawni problemu. (Na x86 niedopasowane dostępy są obsługiwane sprzętowo; jeśli wyłuskujesz int*
wskaźnik wskazujący na nieparzysty adres, będzie on trochę wolniejszy niż gdyby był odpowiednio wyrównany, ale otrzymasz poprawny wynik).
W niektórych innych systemach, takich jak SPARC, próba uzyskania dostępu do źle wyrównanego int
obiektu powoduje błąd magistrali, awarię programu.
Były również systemy, w których niedopasowany dostęp po cichu ignoruje najmniej znaczące bity adresu, powodując dostęp do niewłaściwego fragmentu pamięci.
Rozważ następujący program:
#include <stdio.h>
#include <stddef.h>
int main(void)
{
struct foo {
char c;
int x;
} __attribute__((packed));
struct foo arr[2] = { { 'a', 10 }, {'b', 20 } };
int *p0 = &arr[0].x;
int *p1 = &arr[1].x;
printf("sizeof(struct foo) = %d\n", (int)sizeof(struct foo));
printf("offsetof(struct foo, c) = %d\n", (int)offsetof(struct foo, c));
printf("offsetof(struct foo, x) = %d\n", (int)offsetof(struct foo, x));
printf("arr[0].x = %d\n", arr[0].x);
printf("arr[1].x = %d\n", arr[1].x);
printf("p0 = %p\n", (void*)p0);
printf("p1 = %p\n", (void*)p1);
printf("*p0 = %d\n", *p0);
printf("*p1 = %d\n", *p1);
return 0;
}
Na x86 Ubuntu z gcc 4.5.2 generuje następujący wynik:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = 0xbffc104f
p1 = 0xbffc1054
*p0 = 10
*p1 = 20
Na SPARC Solaris 9 z gcc 4.5.1 daje to:
sizeof(struct foo) = 5
offsetof(struct foo, c) = 0
offsetof(struct foo, x) = 1
arr[0].x = 10
arr[1].x = 20
p0 = ffbff317
p1 = ffbff31c
Bus error
W obu przypadkach program jest kompilowany bez dodatkowych opcji gcc packed.c -o packed
.
(Program, który używa pojedynczej struktury, a nie tablicy, nie przedstawia problemu w wiarygodny sposób, ponieważ kompilator może przydzielić strukturę pod nieparzystym adresem, aby element x
członkowski był odpowiednio wyrównany. Z tablicą dwóch struct foo
obiektów, co najmniej jednego lub drugiego będzie mieć niewyrównanego x
członka).
(W tym przypadku p0
wskazuje na nieprawidłowo wyrównany adres, ponieważ wskazuje na upakowany int
element członkowski następujący po char
elemencie. p1
Zdarza się, że jest prawidłowo wyrównany, ponieważ wskazuje na ten sam element w drugim elemencie tablicy, więc char
poprzedzają go dwa obiekty - aw SPARC Solaris tablica arr
wydaje się być przydzielona pod adresem parzystym, ale nie wielokrotnym 4.)
Odnosząc się do członka x
Urządzony struct foo
według nazwy, kompilator wie, że x
jest potencjalnie niewyrównane i wygeneruje dodatkowy kod dostępu do niego prawidłowo.
Gdy adres arr[0].x
lub arr[1].x
został zapisany w obiekcie wskaźnika, ani kompilator, ani uruchomiony program nie wiedzą, że wskazuje na nieprawidłowo wyrównany int
obiekt. Zakłada po prostu, że jest prawidłowo wyrównany, co powoduje (w niektórych systemach) błąd magistrali lub podobną inną awarię.
Uważam, że naprawienie tego w gcc byłoby niepraktyczne. Ogólne rozwiązanie wymagałoby, aby dla każdej próby wyłuskiwania wskaźnika do dowolnego typu z nietrywialnymi wymaganiami dotyczącymi wyrównania albo (a) udowodnić w czasie kompilacji, że wskaźnik nie wskazuje na nieprawidłowo wyrównany element składowy spakowanej struktury lub (b) generowanie obszerniejszego i wolniejszego kodu, który może obsługiwać wyrównane lub źle wyrównane obiekty.
Mam przedstawiła raport gcc błędzie . Jak powiedziałem, nie uważam, aby naprawianie tego było praktyczne, ale dokumentacja powinna o tym wspominać (obecnie tak nie jest).
AKTUALIZACJA : od 20.12.2018 ten błąd jest oznaczony jako NAPRAWIONY. Łatka pojawi się w gcc 9 z dodatkiem nowej -Waddress-of-packed-member
opcji, domyślnie włączonej.
Kiedy brany jest adres spakowanego członka struktury lub unii, może to skutkować niewyrównaną wartością wskaźnika. Ta poprawka dodaje -Waddress-of-spakowany-element członkowski, aby sprawdzić wyrównanie przy przypisaniu wskaźnika i ostrzec nie wyrównany adres, jak również niewyrównany wskaźnik
Właśnie zbudowałem tę wersję gcc ze źródeł. Dla powyższego programu generuje następujące informacje diagnostyczne:
c.c: In function ‘main’:
c.c:10:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
10 | int *p0 = &arr[0].x;
| ^~~~~~~~~
c.c:11:15: warning: taking address of packed member of ‘struct foo’ may result in an unaligned pointer value [-Waddress-of-packed-member]
11 | int *p1 = &arr[1].x;
| ^~~~~~~~~