187 votes

Est-ce que l'attribut __attribute __ ((emballé)) / #pragma de gcc est dangereux?

En C, le compilateur va pondre des membres d'une structure dans l'ordre dans lequel elles sont déclarées, avec éventuellement des octets de remplissage inséré entre les membres, ou après le dernier membre de, pour s'assurer que chaque membre est correctement aligné.

gcc fournit une extension du langage, __attribute__(packed), ce qui indique au compilateur de ne pas insérer de rembourrage, permettant les membres de la structure d'être mal alignées. Par exemple, si le système nécessite normalement toutes int objets ont 4 octets de l'alignement, `__attribute__(packed) peut causer int struct membres à allouer à impaires.

Citant la gcc de la documentation:

Les "paniers" attribut spécifie qu'une variable ou un champ de structure doit avoir le plus petit possible d'alignement--un octet pour une variable, et un peu pour un champ, sauf si vous spécifiez une valeur supérieure à la `alignés' attribut.

Évidemment, l'utilisation de cette extension peut entraîner dans les petites exigences en matière de données, mais plus lente code, le compilateur doit (sur certaines plates-formes) de générer du code pour accéder à un mal alignées membre d'un octet à la fois.

Mais il y a tous les cas où cela est dangereux? Le compilateur génère toujours correct (mais plus lent), le code d'accès mal alignées membres de paniers structures? Est-il encore possible pour lui de le faire dans tous les cas?

172voto

Keith Thompson Points 85120

(Oui, je vais répondre à ma propre question.)

Oui, __attribute__((packed)) est potentiellement dangereux sur certains systèmes. Le symptôme sans doute de ne pas s'afficher sur un système x86, qui rend le problème plus insidieux; tester sur les systèmes x86 ne révèlerai pas le problème. (Sur x86, mal alignées les accès sont traitées dans le matériel; si vous déréférencer un int* pointeur qui pointe vers une drôle de adresse, ce sera un peu plus lent que si il a été correctement aligné, mais vous aurez à obtenir le résultat correct.)

Sur certains systèmes, tels que SPARC, essayant d'accéder à un mal alignées int objet provoque une erreur de bus, le plantage du programme.

Il y a eu aussi des systèmes où le mauvais alignement de l'accès tranquillement ignore les bits de l'adresse, à l'origine pour accéder à la mauvaise partie de la mémoire.

Considérons 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 x86 Ubuntu 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 est le suivant:

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 un seul struct plutôt que le tableau n'est pas fiable présenter le problème, car le compilateur ne peut allouer de la structure sur un impair de l'adresse de l' x membre est correctement aligné. Avec un tableau de deux struct foo objets, l'un ou l'autre aura un mal alignées x membre.)

En se référant à la membre de l' x d'un struct foo par nom, le compilateur sait qu' x est potentiellement mal aligné, et va générer un code supplémentaire pour accéder correctement.

Une fois l'adresse de l' arr[0].x ou arr[1].x a été stockée dans un pointeur d'objet, ni le compilateur ni le programme en cours d'exécution sait qu'il pointe vers un désabusé int objet. Juste suppose qu'il est correctement aligné, résultant (sur certains systèmes) dans une erreur de bus ou de tout autre manquement similaire.

La fixation de ce ccag serait, je crois, être peu pratique. Une solution générale, il faudrait, pour chaque tentative de déréférencer un pointeur vers n'importe quel type de non triviale de l'alignement des exigences (a) prouver au moment de la compilation que le pointeur ne pointe pas vers un désabusé membre d'un pique-struct, ou (b) générer plus volumineux et plus lent code peut gérer soit aligné ou mal alignées objets.

J'ai envoyé un gcc rapport de bug. Comme je l'ai dit, je ne crois pas que c'est pratique pour régler le problème, mais le document doit mentionner qu'il (elle ne dispose actuellement pas).

71voto

Daniel Santos Points 766

Ams a dit ci-dessus, ne prenez pas un pointeur sur un membre d'une structure qui est emballé. C'est tout simplement de jouer avec le feu. Quand vous dites __attribute__((__packed__)) ou #pragma pack(1), ce que vous dites vraiment, c'est "Hey gcc, je sais vraiment ce que je fais." Quand il s'avère que vous ne le faites pas, vous ne pouvez pas, à juste titre, blâmer le compilateur.

Peut-être que nous pouvons blâmer le compilateur pour qu'il est la complaisance. Alors que gcc n'ont un -Wcast-align option, elle n'est pas activée par défaut, ni avec -Wall ou -Wextra. C'est apparemment dû à des développeurs de gcc envisage ce type de code à un état de mort cérébrale "abomination" indigne de traiter -- compréhensible dédain, mais il n'aide pas quand un inexpérimenté programmeur bumbles.

Considérez les points suivants:

struct  __attribute__((__packed__)) my_struct {
    char c;
    int i;
};

struct my_struct a = {'a', 123};
struct my_struct *b = &a;
int c = a.i;
int d = b->i;
int *e __attribute__((aligned(1))) = &a.i;
int *f = &a.i;

Ici, le type d' a est un panier-struct (tel que défini ci-dessus). De même, b est un pointeur vers un pique-struct. Le type de l'expression a.i est (essentiellement) un int l-valeur 1 octet de l'alignement. c et d sont à la fois la normale ints. Lors de la lecture d' a.i, le compilateur génère du code pour les accès non alignés. Quand vous lisez b->i, bs'type ne sait toujours qu'il est emballé, donc pas de problème de leur. e est un pointeur vers un octet-alignés int, de sorte que le compilateur sait comment déréférencement que de bien. Mais quand vous faites la cession f = &a.i, vous êtes le stockage de la valeur d'un non alignés int pointeur dans l'alignement int pointeur de variable, c'est où vous êtes allé mal. Et je suis d'accord, la ccg devrait avoir cet avertissement est activé par défaut (même pas en -Wall ou -Wextra).

53voto

ams Points 10312

Il est parfaitement en sécurité tant que vous avez toujours accès à leurs valeurs à travers la structure via l' . (dot) ou -> de la notation.

Ce qui est pas sûr, c'est prendre le pointeur de données non alignées et puis y accéder sans prendre en compte.

Aussi, même si chaque élément de la structure est connue pour être non alignés, il est connu pour être non alignés d'une manière particulière, de sorte que la structure dans son ensemble doit être aligné comme le compilateur s'attend ou il va y avoir des problèmes (sur certaines plates-formes, ou, dans l'avenir si une nouvelle façon est inventé pour optimiser les accès non alignés).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X