web-dev-qa-db-fra.com

Quel est le besoin d'un tableau avec zéro élément?

Dans le code du noyau Linux, j'ai trouvé la chose suivante que je ne peux pas comprendre.

 struct bts_action {
         u16 type;
         u16 size;
         u8 data[0];
 } __attribute__ ((packed));

Le code est ici: http://lxr.free-electrons.com/source/include/linux/ti_wilink_st.h

Quels sont le besoin et le but d'un tableau de données avec zéro élément?

115
Jeegar Patel

C'est un moyen d'avoir des tailles de données variables, sans avoir à appeler deux fois malloc (kmalloc dans ce cas). Vous l'utiliseriez comme ceci:

struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);

Ce n'était pas standard et était considéré comme un hack (comme l'a dit Aniket), mais c'était normalisé en C99. Le format standard pour cela est maintenant:

struct bts_action {
     u16 type;
     u16 size;
     u8 data[];
} __attribute__ ((packed)); /* Note: the __attribute__ is irrelevant here */

Notez que vous ne mentionnez aucune taille pour le champ data. Notez également que cette variable spéciale ne peut venir qu'à la fin de la structure.


En C99, cette question est expliquée au 6.7.2.1.16 (c'est moi qui souligne):

Dans un cas particulier, le dernier élément d'une structure avec plus d'un membre nommé peut avoir un type de tableau incomplet; cela s'appelle un membre de tableau flexible. Dans la plupart des situations, le membre du tableau flexible est ignoré. En particulier, la taille de la structure est comme si le membre de tableau flexible était omis, sauf qu'il peut avoir plus de remplissage de fin que l'omission impliquerait. Cependant, quand a. L'opérateur (ou ->) a un opérande gauche qui est (un pointeur vers) une structure avec un membre de tableau flexible et l'opérande droit nomme ce membre, il se comporte comme si ce membre était remplacé par le tableau le plus long (avec le même type d'élément ) qui ne rendrait pas la structure plus grande que l'objet auquel on accède; le décalage de la matrice doit rester celui de l'élément de matrice flexible, même s'il diffère de celui de la matrice de remplacement. Si ce tableau ne contient aucun élément, il se comporte comme s'il en avait un, mais le comportement n'est pas défini si une tentative est faite pour accéder à cet élément ou pour générer un pointeur au-delà.

Ou en d'autres termes, si vous avez:

struct something
{
    /* other variables */
    char data[];
}

struct something *var = malloc(sizeof(*var) + extra);

Vous pouvez accéder à var->data Avec des index dans [0, extra). Notez que sizeof(struct something) ne donnera que la taille tenant compte des autres variables, c'est-à-dire donne à data une taille de 0.


Il peut également être intéressant de noter comment la norme donne en fait des exemples de mallocing une telle construction (6.7.2.1.17):

struct s { int n; double d[]; };

int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

Une autre note intéressante de la norme au même endroit est (soulignement le mien):

en supposant que l'appel à malloc réussit, l'objet pointé par p se comporte, dans la plupart des cas, comme si p avait été déclaré comme:

struct { int n; double d[m]; } *p;

(il existe des circonstances dans lesquelles cette équivalence est rompue; en particulier, les décalages du membre d peuvent ne pas être les mêmes ).

131
Shahbaz

C'est un hack en fait, pour GCC ( C9 ) en fait.

Il est également appelé struct hack .

Donc la prochaine fois, je dirais:

struct bts_action *bts = malloc(sizeof(struct bts_action) + sizeof(char)*100);

Cela équivaudra à dire:

struct bts_action{
    u16 type;
    u16 size;
    u8 data[100];
};

Et je peux créer n'importe quel nombre de ces objets struct.

36
Aniket Inge

L'idée est de permettre un tableau de taille variable à la fin de la structure. Vraisemblablement, bts_action est un paquet de données avec un en-tête de taille fixe (les champs type et size) et un membre de taille variable data. En le déclarant comme un tableau de longueur 0, il peut être indexé comme tout autre tableau. Vous alloueriez alors un bts_action struct, disons 1024 octets data taille, comme ceci:

size_t size = 1024;
struct bts_action* action = (struct bts_action*)malloc(sizeof(struct bts_action) + size);

Voir aussi: http://c2.com/cgi/wiki?StructHack

7
sheu

Le code n'est pas valide C ( voir ceci ). Le noyau Linux n'est, pour des raisons évidentes, pas du tout concerné par la portabilité, il utilise donc beaucoup de code non standard.

Ce qu'ils font est une extension non standard GCC avec une taille de tableau 0. Un programme conforme standard aurait écrit u8 data[]; et cela aurait signifié la même chose. Les auteurs du noyau Linux aiment apparemment rendre les choses inutilement compliquées et non standard, si une option pour le faire se révèle.

Dans les normes C plus anciennes, terminer une structure par un tableau vide était connu sous le nom de "piratage de la structure". D'autres ont déjà expliqué son objectif dans d'autres réponses. Le hack de structure, dans la norme C90, était un comportement non défini et pouvait provoquer des plantages, principalement car un compilateur C est libre d'ajouter un nombre illimité d'octets de remplissage à la fin de la structure. Ces octets de remplissage peuvent entrer en collision avec les données que vous avez tenté de "pirater" à la fin de la structure.

GCC a fait très tôt une extension non standard pour changer ce comportement d'un comportement non défini à un comportement bien défini. La norme C99 a ensuite adapté ce concept et tout programme C moderne peut donc utiliser cette fonctionnalité sans risque. Il est connu sous le nom de membre de tableau flexible dans C99/C11.

5
Lundin

Une autre utilisation peu courante du tableau de longueur nulle est d'obtenir une étiquette nommée à l'intérieur d'une structure.

Supposons que vous ayez quelques grandes définitions de structure (s'étendant sur plusieurs lignes de cache) que vous souhaitez vous assurer qu'elles sont alignées sur la limite de ligne de cache au début et au milieu où elle traverse la limite.

struct example_large_s
{
    u32 first; // align to CL
    u32 data;
    ....
    u64 *second;  // align to second CL after the first one
    ....
};

Dans le code, vous pouvez les déclarer en utilisant des extensions GCC comme:

__attribute__((aligned(CACHE_LINE_BYTES)))

Mais vous voulez toujours vous assurer que cela est appliqué lors de l'exécution.

ASSERT (offsetof (example_large_s, first) == 0);
ASSERT (offsetof (example_large_s, second) == CACHE_LINE_BYTES);

Cela fonctionnerait pour une seule structure, mais il serait difficile de couvrir de nombreuses structures, chacune ayant un nom de membre différent à aligner. Vous obtiendrez très probablement du code comme ci-dessous où vous devez trouver les noms du premier membre de chaque structure:

assert (offsetof (one_struct,     <name_of_first_member>) == 0);
assert (offsetof (one_struct,     <name_of_second_member>) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, <name_of_first_member>) == 0);
assert (offsetof (another_struct, <name_of_second_member>) == CACHE_LINE_BYTES);

Au lieu de procéder de cette façon, vous pouvez déclarer un tableau de longueur nulle dans la structure agissant comme une étiquette nommée avec un nom cohérent mais ne consomme aucun espace.

#define CACHE_LINE_ALIGN_MARK(mark) u8 mark[0] __attribute__((aligned(CACHE_LINE_BYTES)))
struct example_large_s
{
    CACHE_LINE_ALIGN_MARK (cacheline0);
    u32 first; // align to CL
    u32 data;
    ....
    CACHE_LINE_ALIGN_MARK (cacheline1);
    u64 *second;  // align to second CL after the first one
    ....
};

Ensuite, le code d'assertion d'exécution serait beaucoup plus facile à maintenir:

assert (offsetof (one_struct,     cacheline0) == 0);
assert (offsetof (one_struct,     cacheline1) == CACHE_LINE_BYTES);
assert (offsetof (another_struct, cacheline0) == 0);
assert (offsetof (another_struct, cacheline1) == CACHE_LINE_BYTES);
1
Wei Shen