Sì, __attribute__((packed))
potenzialmente non è sicuro su alcuni sistemi. Il sintomo probabilmente non si presenterà su un x86, il che rende il problema più insidioso; i test sui sistemi x86 non rivelano il problema. (Sulla x86, gli accessi disallineati sono gestiti nell'hardware; se si fa riferimento a un int*
puntatore che punta a un indirizzo dispari, sarà un po 'più lento rispetto a se fosse correttamente allineato, ma si otterrà il risultato corretto.)
Su alcuni altri sistemi, come SPARC, il tentativo di accedere a un int
oggetto non allineato provoca un errore del bus, causando l'arresto anomalo del programma.
Ci sono stati anche sistemi in cui un accesso disallineato ignora silenziosamente i bit di ordine inferiore dell'indirizzo, causando l'accesso a un blocco di memoria errato.
Considera il seguente programma:
#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;
}
Su x86 Ubuntu con gcc 4.5.2, produce il seguente output:
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
Su SPARC Solaris 9 con gcc 4.5.1, produce quanto segue:
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
In entrambi i casi, il programma è compilato senza opzioni extra, solo gcc packed.c -o packed
.
(Un programma che utilizza una singola struttura anziché una matrice non presenta in modo affidabile il problema, poiché il compilatore può allocare la struttura su un indirizzo dispari in modo che il x
membro sia correttamente allineato. Con una matrice di due struct foo
oggetti, almeno uno o l'altro avrà un x
membro disallineato .)
(In questo caso, p0
punta a un indirizzo disallineato, perché punta a un int
membro impacchettato che segue un char
membro. p1
Sembra essere allineato correttamente, poiché punta allo stesso membro nel secondo elemento dell'array, quindi ci sono due char
oggetti che lo precedono - e su SPARC Solaris l'array arr
sembra essere assegnato a un indirizzo che è pari, ma non un multiplo di 4.)
Quando si riferisce al membro x
di un struct foo
nome, il compilatore sa che x
è potenzialmente disallineato e genererà un codice aggiuntivo per accedervi correttamente.
Una volta che l'indirizzo di arr[0].x
o arr[1].x
è stato memorizzato in un oggetto puntatore, né il compilatore né il programma in esecuzione sanno che punta a un int
oggetto non allineato . Presuppone solo che sia correttamente allineato, risultando (su alcuni sistemi) in un errore del bus o altro errore simile.
Risolvere questo problema in CCG sarebbe, credo, poco pratico. Una soluzione generale richiederebbe, per ogni tentativo di dereferenziare un puntatore a qualsiasi tipo con requisiti di allineamento non banali (a) dimostrando in fase di compilazione che il puntatore non punta a un membro disallineato di una struttura impacchettata, o (b) generare codice più voluminoso e più lento in grado di gestire oggetti allineati o disallineati.
Ho inviato una segnalazione di bug gcc . Come ho detto, non credo sia pratico risolverlo, ma la documentazione dovrebbe menzionarlo (al momento non lo fa).
AGGIORNAMENTO : A partire dal 20-12-2018, questo errore è contrassegnato come FISSO. La patch apparirà in gcc 9 con l'aggiunta di una nuova -Waddress-of-packed-member
opzione, abilitata di default.
Quando viene preso l'indirizzo del membro impacchettato di struct o union, può risultare in un valore puntatore non allineato. Questa patch aggiunge -Waddress-of-pacchetto-member per verificare l'allineamento durante l'assegnazione del puntatore e avvisare l'indirizzo non allineato e il puntatore non allineato
Ho appena creato quella versione di gcc dal sorgente. Per il programma sopra, produce questi diagnostici:
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;
| ^~~~~~~~~