Est-ce que l’utilisation de membres de tableaux flexibles en C est une mauvaise pratique?

J’ai récemment lu que l’utilisation de membres de baies flexibles en C était une mauvaise pratique d’ingénierie logicielle. Cependant, cette déclaration n’a été soutenue par aucun argument. Est-ce un fait accepté?

( Les membres de tableau flexibles sont une fonctionnalité C introduite dans C99 par laquelle on peut déclarer le dernier élément comme un tableau de taille non spécifiée. Par exemple:)

struct header { size_t len; unsigned char data[]; }; 

C’est un “fait” accepté que l’utilisation de goto est une mauvaise pratique de l’ingénierie logicielle. Cela ne le rend pas vrai. Il y a des moments où goto est utile, en particulier lors du nettoyage et lors du portage à partir de l’assembleur.

Les membres de la baie de disques flexibles me semblent particulièrement utiles, car ils mappent les formats de données hérités tels que les formats de modèles de fenêtres sur RiscOS. Ils auraient été extrêmement utiles pour cela il y a une quinzaine d’années, et je suis sûr qu’il y a encore des gens qui s’occupent de telles choses et qui les trouveraient utiles.

Si utiliser des membres de tableau flexibles est une mauvaise pratique, alors je suggère que nous allions tous dire aux auteurs de la spécification C99 ceci. Je pense qu’ils pourraient avoir une réponse différente.

La raison pour laquelle je donnerais pour ne pas le faire est que cela ne vaut pas la peine d’attacher votre code à C99 juste pour utiliser cette fonctionnalité.

Le fait est que vous pouvez toujours utiliser l’idiome suivant:

 struct header { size_t len; unsigned char data[1]; }; 

C’est entièrement portable. Ensuite, vous pouvez prendre le 1 en compte lors de l’allocation de la mémoire pour n éléments dans les data du tableau:

 ptr = malloc(sizeof(struct header) + (n-1)); 

Si vous avez déjà besoin de C99 pour créer votre code pour toute autre raison ou si vous ciblez un compilateur spécifique, je ne vois aucun inconvénient.

Vous vouliez…

 struct header { size_t len; unsigned char data[]; }; 

En C, c’est un idiome commun. Je pense que de nombreux compilateurs acceptent également:

  unsigned char data[0]; 

Oui, c’est dangereux, mais là encore, ce n’est vraiment pas plus dangereux que les tableaux C normaux – c’est-à-dire très dangereux ;-). Utilisez-le avec soin et uniquement dans des circonstances où vous avez vraiment besoin d’un tableau de taille inconnue. Assurez-vous que malloc et libérez la mémoire correctement, en utilisant quelque chose comme: –

  foo = malloc(sizeof(header) + N * sizeof(data[0])); foo->len = N; 

Une alternative consiste à faire des données simplement un pointeur vers les éléments. Vous pouvez ensuite realloc () les données à la bonne taille si nécessaire.

  struct header { size_t len; unsigned char *data; }; 

Bien sûr, si vous posiez des questions sur C ++, l’une ou l’autre serait une mauvaise pratique. Ensuite, vous utiliseriez généralement des vecteurs STL.

J’ai vu quelque chose comme ceci: de l’interface C et de l’implémentation.

  struct header { size_t len; unsigned char *data; }; struct header *p; p = malloc(sizeof(*p) + len + 1 ); p->data = (unsigned char*) (p + 1 ); // memory after p is mine! 

Remarque: les données ne doivent pas nécessairement être le dernier membre.

En guise de remarque, pour une compatibilité C89, une telle structure devrait être allouée comme suit:

 struct header *my_header = malloc(offsetof(struct header, data) + n * sizeof my_header->data); 

Ou avec des macros:

 #define FLEXIBLE_SIZE SIZE_MAX /* or whatever maximum length for an array */ #define SIZEOF_FLEXIBLE(type, member, length) \ ( offsetof(type, member) + (length) * sizeof ((type *)0)->member[0] ) struct header { size_t len; unsigned char data[FLEXIBLE_SIZE]; }; ... size_t n = 123; struct header *my_header = malloc(SIZEOF_FLEXIBLE(struct header, data, n)); 

En définissant FLEXIBLE_SIZE sur SIZE_MAX, vous êtes presque certain que cela échouera:

 struct header *my_header = malloc(sizeof *my_header); 

Il existe des inconvénients liés à la façon dont les structures sont parfois utilisées, et cela peut être dangereux si vous ne réfléchissez pas aux implications.

Pour votre exemple, si vous lancez une fonction:

 void test(void) { struct header; char *p = &header.data[0]; ... } 

Ensuite, les résultats ne sont pas définis (car aucun stockage n’a été alloué pour les données). C’est quelque chose dont vous serez normalement au courant, mais il y a des cas où les programmeurs C sont probablement habitués à pouvoir utiliser la sémantique de valeur pour les structures, qui se décompose de différentes manières.

Par exemple, si je définis:

 struct header2 { int len; char data[MAXLEN]; /* MAXLEN some appropriately large number */ } 

Ensuite, je peux copier deux instances simplement par affectation, à savoir:

 struct header2 inst1 = inst2; 

Ou si elles sont définies comme des pointeurs:

 struct header2 *inst1 = *inst2; 

Cela ne fonctionnera toutefois pas, car les data tableau de variables data sont pas copiées. Ce que vous voulez, c’est de dynamiser dynamicment la taille de la structure et de copier le tableau avec memcpy ou équivalent.

De même, écrire une fonction qui accepte une structure ne fonctionnera pas, car les arguments dans les appels de fonction sont, à nouveau, copiés par valeur, et donc ce que vous obtiendrez ne sera probablement que le premier élément des data de votre tableau.

Cela ne veut pas dire que ce soit une mauvaise idée à utiliser, mais vous devez garder à l’esprit de toujours allouer dynamicment ces structures et de ne les transmettre qu’en tant que pointeurs.

Non, l’utilisation de membres de tableau flexibles en C n’est pas une mauvaise pratique.

Cette fonctionnalité de langue a été normalisée pour la première fois dans ISO C99, 6.7.2.1 (16). Pour la norme actuelle, ISO C11, elle est spécifiée à la section 6.7.2.1 (18).

Vous pouvez les utiliser comme ceci:

 struct Header { size_t d; long v[]; }; typedef struct Header Header; size_t n = 123; // can dynamically change during program execution // ... Header *h = malloc(sizeof(Header) + sizeof(long[n])); h->n = n; 

Vous pouvez également l’allouer comme suit:

 Header *h = malloc(sizeof *h + n * sizeof h->v[0]); 

Notez que sizeof(Header) inclut des octets de remplissage éventuels. Par conséquent, l’allocation suivante est incorrecte et peut générer un dépassement de tampon:

 Header *h = malloc(sizeof(size_t) + sizeof(long[n])); // invalid! 

Une structure avec un groupe de membres flexible réduit le nombre d’allocations par 1/2, c’est-à-dire au lieu de 2 allocations pour un object struct dont vous avez besoin de seulement 1. Ainsi, si vous devez allouer un grand nombre de ces instances de structure le temps d’exécution de votre programme (par un facteur constant).

Contrairement à cela, l’utilisation de constructions non standardisées pour les membres de tableau flexibles qui génèrent un comportement indéfini (par exemple, dans long v[0]; ou long v[1]; ) est évidemment une mauvaise pratique. Ainsi, tout comportement non défini doit être évité.

Depuis la publication de la norme ISO C99 en 1999, il y a près de 20 ans, lutter pour la compatibilité avec ISO C89 est un argument faible.