Oui, __attribute__((packed))
est potentiellement dangereux sur certains systèmes. Le symptôme n'apparaîtra probablement pas sur un x86, ce qui rend simplement le problème plus insidieux; les tests sur les systèmes x86 ne révéleront pas le problème. (Sur le x86, les accès mal alignés sont gérés par le matériel; si vous déréférencer un int*
pointeur qui pointe vers une adresse impaire, il sera un peu plus lent que s'il était correctement aligné, mais vous obtiendrez le résultat correct.)
Sur certains autres systèmes, tels que SPARC, la tentative d'accès à un int
objet mal aligné provoque une erreur de bus, bloquant le programme.
Il existe également des systèmes dans lesquels un accès mal aligné ignore silencieusement les bits de poids faible de l'adresse, ce qui l'amène à accéder au mauvais bloc de mémoire.
Considérez le programme suivant:
#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;
}
Sur Ubuntu x86 avec gcc 4.5.2, il produit la sortie suivante:
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
Sur SPARC Solaris 9 avec gcc 4.5.1, il produit les éléments suivants:
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
Dans les deux cas, le programme est compilé sans options supplémentaires, juste gcc packed.c -o packed
.
(Un programme qui utilise une structure unique plutôt qu'un tableau ne présente pas le problème de manière fiable, car le compilateur peut allouer la structure sur une adresse impaire afin que le x
membre soit correctement aligné. Avec un tableau de deux struct foo
objets, au moins l'un ou l'autre aura un x
membre mal aligné .)
(Dans ce cas, p0
pointe vers une adresse mal alignée, car elle pointe vers un int
membre compact qui suit un char
membre. p1
Se trouve être correctement alignée, car elle pointe vers le même membre dans le deuxième élément du tableau, donc il y a deux char
objets qui le précèdent - et sur SPARC Solaris, la baie arr
semble être allouée à une adresse paire, mais pas un multiple de 4.)
Lorsqu'il fait référence au membre x
d'un struct foo
par son nom, le compilateur sait qu'il x
est potentiellement mal aligné et générera du code supplémentaire pour y accéder correctement.
Une fois que l'adresse de arr[0].x
ou arr[1].x
a été stockée dans un objet pointeur, ni le compilateur ni le programme en cours d'exécution ne savent qu'il pointe vers un int
objet mal aligné . Il suppose simplement qu'il est correctement aligné, ce qui entraîne (sur certains systèmes) une erreur de bus ou une autre panne similaire.
Corriger cela dans gcc serait, je crois, irréalisable. Une solution générale exigerait, pour chaque tentative de déréférencer un pointeur vers n'importe quel type avec des exigences d'alignement non triviales, soit (a) prouvant au moment de la compilation que le pointeur ne pointe pas vers un membre mal aligné d'une structure compressée, ou (b) générer un code plus volumineux et plus lent qui peut gérer des objets alignés ou mal alignés.
J'ai soumis un rapport de bogue gcc . Comme je l'ai dit, je ne pense pas qu'il soit pratique de le réparer, mais la documentation devrait le mentionner (ce n'est actuellement pas le cas).
MISE À JOUR : à partir du 20/12/2018, ce bogue est marqué comme FIXE. Le patch apparaîtra dans gcc 9 avec l'ajout d'une nouvelle -Waddress-of-packed-member
option, activée par défaut.
Lorsque l'adresse du membre compressé de struct ou union est prise, cela peut entraîner une valeur de pointeur non alignée. Ce correctif ajoute -Waddress-of-packing-member pour vérifier l'alignement lors de l'affectation du pointeur et avertir les adresses non alignées ainsi que les pointeurs non alignés
Je viens de construire cette version de gcc à partir des sources. Pour le programme ci-dessus, il produit ces diagnostics:
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;
| ^~~~~~~~~