Les membres de groupe flexibles peuvent conduire à un comportement indéfini?

  1. En utilisant des membres de groupe flexibles (FAM) dans les types de structure, exposons-nous nos programmes à la possibilité d’un comportement indéfini?

  2. Est-il possible pour un programme d’utiliser des FAM et de restr un programme ssortingctement conforme?

  3. Le décalage du membre de groupe flexible doit-il être à la fin de la structure?

Les questions concernent à la fois C99 (TC3) et C11 (TC1) .

 #include  #include  #include  int main(void) { struct s { size_t len; char pad; int array[]; }; struct s *s = malloc(sizeof *s + sizeof *s->array); printf("sizeof *s: %zu\n", sizeof *s); printf("offsetof(struct s, array): %zu\n", offsetof(struct s, array)); s->array[0] = 0; s->len = 1; printf("%d\n", s->array[0]); free(s); return 0; } 

Sortie:

 sizeof *s: 16 offsetof(struct s, array): 12 0 

La réponse courte

  1. Oui. Les conventions courantes d’utilisation de FAM exposent nos programmes à la possibilité d’un comportement indéfini. Cela dit, je ne suis au courant d’aucune implémentation conforme existante qui se comporterait mal.

  2. Possible, mais improbable. Même si nous n’atteignons pas réellement un comportement indéfini, nous risquons toujours d’échouer à une conformité ssortingcte.

  3. Non, il n’est pas nécessaire que l’offset du FAM soit à la fin de la structure, il peut recouvrir tous les octets de remplissage de fin.

Les réponses s’appliquent à la fois à C99 (TC3) et à C11 (TC1) .


La longue réponse

Les FAM ont été introduits pour la première fois dans la C99 (TC0) (décembre 1999), et leurs spécifications originales exigeaient que l’offset du FAM se trouve à la fin de la structure. La spécification d’origine était bien définie et, en tant que telle, ne pouvait pas conduire à un comportement indéfini ou poser un problème de conformité ssortingcte.

C99 (TC0) §6.7.2.1 p16 (déc 1999)

[Ce document est la norme officielle, il est protégé par le droit d’auteur et n’est pas librement disponible.]

Le problème était que les implémentations courantes de C99, telles que GCC, ne respectaient pas l’exigence de la norme et permettaient au FAM de superposer les derniers octets de remplissage. Leur approche a été jugée plus efficace et, pour qu’ils respectent les exigences de la norme, le comité a décidé de modifier les spécifications et à partir de C99 TC2 (novembre 2004), la norme n’était plus nécessaire. l’offset du FAM doit être à la fin de la structure.

C99 (TC2) §6.7.2.1 p16 (nov. 2004)

[…] la taille de la structure est la même que si le membre du groupe flexible était omis, à l’exception du fait qu’il pourrait y avoir plus de remplissage de fin que ce que l’omission impliquerait.

La nouvelle spécification supprimait l’affirmation exigeant que l’offset de FAM fût à la fin de la structure et introduisait une conséquence très regrettable, car la norme donne à l’implémentation la liberté de ne pas conserver les valeurs des octets de remplissage dans les structures ou les syndicats dans un état cohérent. Plus précisement:

C99 (TC3) §6.2.6.1 p6

Lorsqu’une valeur est stockée dans un object de type structure ou union, y compris dans un object membre, les octets de la représentation de l’object correspondant aux octets de remplissage prennent des valeurs non spécifiées.

Cela signifie que si l’un de nos éléments FAM correspond (ou recouvre) l’un des octets de remplissage de fin, lors de l’enregistrement dans un membre de la structure, ils peuvent (éventuellement) prendre des valeurs non spécifiées. Nous n’avons même pas besoin de nous demander si cela s’applique à une valeur stockée dans le FAM lui-même, même l’interprétation ssortingcte que cela ne s’applique qu’aux membres autres que le FAM est suffisamment préjudiciable.

 #include  #include  #include  int main(void) { struct s { size_t len; char pad; int array[]; }; struct s *s = malloc(sizeof *s + sizeof *s->array); if (sizeof *s > offsetof(struct s, array)) { s->array[0] = 123; s->len = 1; /* any padding bytes take unspecified values */ printf("%d\n", s->array[0]); /* indeterminate value */ } free(s); return 0; } 

Une fois que nous avons stocké dans un membre de la structure, les octets de remplissage prennent des octets non spécifiés. Par conséquent, toute hypothèse émise sur les valeurs des éléments FAM correspondant aux octets de remplissage suivants est désormais fausse. Ce qui signifie que toute hypothèse nous conduit à échouer à une ssortingcte conformité.

Comportement indéfini

Bien que les valeurs des octets de remplissage soient des “valeurs non spécifiées”, on ne peut pas en dire autant du type qu’elles affectent, car une représentation d’object basée sur des valeurs non spécifiées peut générer une représentation d’interruption. Ainsi, le seul terme standard qui décrit ces deux possibilités serait “valeur indéterminée”. Si le type de FAM a des représentations d’interruption, son access ne concerne pas uniquement une valeur non spécifiée, mais un comportement non défini.

Mais attendez, il y a plus. Si nous convenons que le seul terme standard pour décrire cette valeur est une “valeur indéterminée”, alors même si le type de FAM ne contient pas de représentation de piège, nous avons atteint un comportement indéfini, car l’interprétation officielle du C Selon le comité des normes, le fait de transmettre des valeurs indéterminées aux fonctions de bibliothèque standard constitue un comportement indéfini.

C’est une longue réponse qui traite longuement d’un sujet délicat.

TL; DR

Je ne suis pas d’accord avec l’ parsing de Dror K.

Le problème clé est une incompréhension de ce que signifie le §6.2.1.6 des normes C99 et C11, et son application inappropriée à une assignation d’entier simple telle que:

 fam_ptr->nonfam_member = 23; 

Cette affectation n’est pas autorisée à modifier les octets de remplissage de la structure pointée par fam_ptr . Par conséquent, l’parsing basée sur la présomption que cela peut modifier les octets de remplissage de la structure est erronée.

Contexte

En principe, je ne suis pas terriblement préoccupé par la norme C99 et ses corrigenda; ils ne sont pas la norme actuelle. Cependant, l’évolution de la spécification des membres du tableau flexible est informative.

La norme C99 – ISO / IEC 9899: 1999 – comportait 3 rectificatifs techniques:

  • TC1 publié le 2001-09-01 (7 pages),
  • TC2 publié le 15-11-2004 (15 pages),
  • TC3 publié le 2007-11-15 (10 pages).

C’est par exemple le TC3 qui a déclaré que gets() était obsolète et obsolète, ce qui a entraîné son retrait de la norme C11.

La norme C11 – ISO / IEC 9899: 2011 – comporte un corrigendum technique, mais il définit simplement la valeur de deux macros laissées accidentellement sous la forme 201ymmL – les valeurs requirejses pour __STDC_VERSION__ et __STDC_LIB_EXT1__ ont été corrigées à la valeur 201112L . (Vous pouvez voir le TC1 – officiellement “ISO / IEC 9899: 2011 / Cor.1: 2012 (fr) Technologies de l’information – Langages de programmation – CORRIGENDUM TECHNIQUE C 1” – à l’ adresse https://www.iso.org/obp/ui / # iso: std: iso-iec: 9899: ed-3: v1: cor: 1: v1: fr Je n’ai pas encore trouvé comment vous pouvez le télécharger, mais c’est tellement simple que ça ne marche vraiment pas. t importe beaucoup.

Norme C99 sur les membres de groupe flexibles

ISO / CEI 9899: 1999 (avant TC2) § 6.7.2.1 §16:

En tant que cas particulier, le dernier élément d’une structure comportant plusieurs membres nommés peut avoir un type de tableau incomplet; c’est ce qu’on appelle un membre de tableau flexible . À deux exceptions près, le membre de tableau flexible est ignoré. Premièrement, la taille de la structure doit être égale à l’offset du dernier élément d’une structure par ailleurs identique qui remplace le membre de tableau flexible par un tableau de longueur non spécifiée. 106) Deuxièmement, quand a . (ou -> ) L’opérateur a un opérande gauche qui est (un pointeur sur) une structure avec un membre de tableau flexible et l’opérande de droite 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’object auquel on accède; le décalage du tableau doit restr celui du membre du tableau flexible, même s’il serait différent de celui du tableau de remplacement. Si ce tableau ne contient aucun élément, il se comporte comme s’il ne contenait qu’un élément, mais le comportement n’est pas défini si une tentative quelconque est faite pour accéder à cet élément ou pour générer un pointeur un par un.

126) La longueur n’est pas spécifiée pour tenir compte du fait que les implémentations peuvent donner aux membres de la masortingce des alignements différents en fonction de leur longueur.

(Cette note de bas de page est supprimée lors de la réécriture.) La norme C99 d’origine comprenait un exemple:

¶17 EXEMPLE En supposant que tous les membres du groupe sont alignés de la même manière, après les déclarations:

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

les trois expressions:

 sizeof (struct s) offsetof(struct s, d) offsetof(struct ss, d) 

avoir la même valeur. La structure struct s a un membre de tableau flexible d.

¶18 Si sizeof (double) est 8, alors, après l’exécution du code suivant:

 struct s *s1; struct s *s2; s1 = malloc(sizeof (struct s) + 64); s2 = malloc(sizeof (struct s) + 46); 

et en supposant que les appels à malloc aboutissent, les objects désignés par s1 et s2 se comportent comme si les identificateurs avaient été déclarés comme:

 struct { int n; double d[8]; } *s1; struct { int n; double d[5]; } *s2; 

¶19 Après les autres missions réussies:

 s1 = malloc(sizeof (struct s) + 10); s2 = malloc(sizeof (struct s) + 6); 

ils se comportent alors comme si les déclarations étaient:

 struct { int n; double d[1]; } *s1, *s2; 

et:

 double *dp; dp = &(s1->d[0]); // valid *dp = 42; // valid dp = &(s2->d[0]); // valid *dp = 42; // undefined behavior 

¶20 La mission:

 *s1 = *s2; 

ne copie que le membre n et aucun des éléments du tableau. De même:

 struct s t1 = { 0 }; // valid struct s t2 = { 2 }; // valid struct ss tt = { 1, { 4.2 }}; // valid struct s t3 = { 1, { 4.2 }}; // invalid: there is nothing for the 4.2 to initialize t1.n = 4; // valid t1.d[0] = 4.2; // undefined behavior 

Une partie de cet exemple de matériel a été supprimée dans C11. Le changement n’a pas été noté (et n’a pas besoin d’être noté) dans TC2 car les exemples ne sont pas normatifs. Mais le matériel réécrit dans C11 est informatif lorsqu’il est étudié.

Papier N983 identifiant un problème avec les membres de la masortingce

Le numéro N983 du mailing du groupe de travail 14 pré-Santa Cruz-2002 est, je crois, la déclaration initiale d’un rapport de défaut. Il indique que certains compilateurs C (en citant trois) parviennent à placer un FAM avant le remplissage à la fin d’une structure. Le rapport de défaut final était le DR 282 .

Si je comprends bien, ce rapport a conduit au changement de TC2, bien que je n’aie pas tracé toutes les étapes du processus. Il semble que le DR n’est plus disponible séparément.

TC2 a utilisé le libellé de la norme C11 figurant dans le document normatif.

Norme C11 sur les membres de groupe flexibles

Alors, que dit la norme C11 à propos des membres de groupe flexibles?

§6.7.2.1 Spécificateurs de structure et d’union

¶3 Une structure ou une union ne doit pas contenir un membre avec un type incomplet ou fonction (par conséquent, une structure ne doit pas contenir une instance de elle-même, mais peut contenir un pointeur sur une instance de elle-même), sauf que le dernier membre d’une structure avec plusieurs membres nommés peuvent avoir un type de tableau incomplet; une telle structure (et toute union contenant, éventuellement, de manière récursive, un membre qui est une telle structure) ne doit pas être un membre d’une structure ou un élément d’un tableau.

Cela positionne fermement le FAM à la fin de la structure – «le dernier membre» est par définition à la fin de la structure, ce qui est confirmé par:

¶15 Dans un object de structure, les membres autres que les champs de bits et les unités dans lesquelles résident les champs de bits ont des adresses qui augmentent dans l’ordre dans lequel ils ont été déclarés.

¶17 Il peut y avoir un remplissage non nommé à la fin d’une structure ou d’une union.

¶18 Comme cas particulier, le dernier élément d’une structure comportant plusieurs membres nommés peut avoir un type de tableau incomplet; c’est ce qu’on appelle un membre de tableau flexible . Dans la plupart des cas, le membre de groupe flexible est ignoré. En particulier, la taille de la structure est la même que si le membre de groupe flexible était omis, à l’exception du fait qu’il pourrait y avoir plus de remplissage de fin que ce que l’omission impliquerait. Cependant, quand a . (ou -> ) L’opérateur a un opérande gauche qui est (un pointeur sur) une structure avec un membre de tableau flexible et l’opérande de droite 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’object auquel on accède; le décalage du tableau doit restr celui du membre du tableau flexible, même s’il serait différent de celui du tableau de remplacement. Si ce tableau ne contient aucun élément, il se comporte comme s’il ne contenait qu’un élément, mais le comportement n’est pas défini si une tentative quelconque est faite pour accéder à cet élément ou pour générer un pointeur un par un.

Ce paragraphe contient la modification au paragraphe 20 de l’ISO / CEI 9899: 1999 / Cor.2: 2004 (E) – le TC2 pour C99;

Les données à la fin de la partie principale d’une structure contenant un membre de groupe flexible constituent un remplissage de fin de fichier normal pouvant survenir avec tout type de structure. Ce remplissage ne peut pas être utilisé légitimement, mais peut être transmis à des fonctions de bibliothèque, etc. via des pointeurs sur la structure, sans entraîner de comportement indéfini.

La norme C11 contient trois exemples, mais le premier et le troisième concernent des structures et des syndicats anonymes plutôt que les mécanismes des membres de groupe flexibles. N’oubliez pas que les exemples ne sont pas «normatifs», mais ils sont illustratifs.

¶20 EXEMPLE 2 Après la déclaration:

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

la structure struct s a un membre de tableau flexible d . Une façon typique de l’utiliser est:

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

et en supposant que l’appel à malloc , l’object désigné 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 compensations du membre d pourraient ne pas être les mêmes).

¶21 Suite à la déclaration ci-dessus:

 struct s t1 = { 0 }; // valid struct s t2 = { 1, { 4.2 }}; // invalid t1.n = 4; // valid t1.d[0] = 4.2; // might be undefined behavior 

L’initialisation de t2 n’est pas valide (et viole une contrainte) car la struct s est traitée comme si elle ne contenait pas le membre d . L’atsortingbution à t1.d[0] est probablement un comportement indéfini, mais il est possible que

 sizeof (struct s) >= offsetof(struct s, d) + sizeof (double) 

auquel cas la cession serait légitime. Néanmoins, il ne peut pas apparaître dans un code ssortingctement conforme.

¶22 Après la déclaration suivante:

 struct ss { int n; }; 

les expressions:

 sizeof (struct s) >= sizeof (struct ss) sizeof (struct s) >= offsetof(struct s, d) 

sont toujours égaux à 1.

¶23 Si sizeof (double) est 8, alors, après l’exécution du code suivant:

 struct s *s1; struct s *s2; s1 = malloc(sizeof (struct s) + 64); s2 = malloc(sizeof (struct s) + 46); 

et en supposant que les appels à malloc aboutissent, les objects s1 par s1 et s2 se comportent, dans la plupart des cas, comme si les identificateurs avaient été déclarés comme:

 struct { int n; double d[8]; } *s1; struct { int n; double d[5]; } *s2; 

¶24 Suite aux autres missions réussies:

 s1 = malloc(sizeof (struct s) + 10); s2 = malloc(sizeof (struct s) + 6); 

ils se comportent alors comme si les déclarations étaient:

 struct { int n; double d[1]; } *s1, *s2; 

et:

 double *dp; dp = &(s1->d[0]); // valid *dp = 42; // valid dp = &(s2->d[0]); // valid *dp = 42; // undefined behavior 

¶25 La mission:

 *s1 = *s2; 

ne copie que le membre n ; Si l’un des éléments du tableau se trouve dans les premiers octets sizeof (struct s) de la structure, ils peuvent être copiés ou simplement remplacés par des valeurs indéterminées.

Notez que cela a changé entre C99 et C11.

Une autre partie de la norme décrit ce comportement de copie:

§6.2.6 Représentation des types §6.2.6.1 Généralités

¶6 Lorsqu’une valeur est stockée dans un object de type structure ou union, y compris dans un object membre, les octets de la représentation de l’object correspondant aux octets de remplissage prennent des valeurs non spécifiées. 51) La valeur d’une structure ou d’un object d’union n’est jamais une représentation de piège, même si la valeur d’un membre de la structure ou d’un object d’union peut être une représentation de piège.

51) Ainsi, par exemple, l’affectation de structure n’a pas besoin de copier de bits de remplissage.

Illustrer la structure FAM problématique

Dans la salle de discussion C , j’ai écrit quelques informations dont il s’agit paraphraser:

Considérer:

 struct fam1 { double d; char c; char fam[]; }; 

En supposant que double nécessite un alignement sur 8 octets (ou 4 octets; peu importe, mais je vais m’en tenir à 8), alors struct non_fam1a { double d; char c; }; struct non_fam1a { double d; char c; }; aurait 7 octets de remplissage après c et une taille de 16. En outre, struct non_fam1b { double d; char c; char nonfam[4]; }; struct non_fam1b { double d; char c; char nonfam[4]; }; aurait 3 octets de remplissage après le tableau nonfam , et une taille de 16.

La suggestion est que le début de la fam dans la struct fam1 peut être à l’offset 9, même si sizeof(struct fam1) est sizeof(struct fam1) 16. Ainsi, les octets après c ne remplissent pas (nécessairement).

Ainsi, pour un FAM assez petit, la taille de la structure plus FAM peut être inférieure à la taille de la struct fam .

L’allocation prototype est:

 struct fam1 *fam = malloc(sizeof(struct fam1) + array_size * sizeof(char)); 

quand FAM est de type char (comme dans struct fam1 ). C’est une surestimation (brute) lorsque le décalage de fam est inférieur à sizeof(struct fam1) .

Dror K. a souligné :

Il existe des macros permettant de calculer le stockage “précis” requirejs en fonction de décalages FAM inférieurs à la taille de la structure. Comme celui-ci: https://gustedt.wordpress.com/2011/03/14/flexible-array-member/

Répondre à la question

La question demande:

  1. En utilisant des membres de groupe flexibles (FAM) dans les types de structure, exposons-nous nos programmes à la possibilité d’un comportement indéfini?
  2. Est-il possible pour un programme d’utiliser des FAM et de restr un programme ssortingctement conforme?
  3. Le décalage du membre de groupe flexible doit-il être à la fin de la structure?

Les questions concernent à la fois C99 (TC3) et C11 (TC1).

Je crois que si vous codez correctement, les réponses sont “Non”, “Oui”, “Non et Oui, selon…”.

question 1

Je suppose que le but de la question 1 est “votre programme doit-il inévitablement être exposé à un comportement indéfini si vous utilisez un FAM quelque part?” Pour énoncer ce que je pense est évident: il existe de nombreuses façons d’exposer un programme à un comportement indéfini (et certaines de ces méthodes impliquent des structures avec des membres de tableau souples).

Je ne pense pas que le simple fait d’utiliser un FAM signifie que le programme a automatiquement (invoque, est exposé à) un comportement indéfini.

question 2

La section §4 Conformité définit:

¶5 Un programme ssortingctement conforme ne doit utiliser que les fonctionnalités de la langue et de la bibliothèque spécifiées dans la présente Norme internationale. 3) Il ne doit pas produire de sortie dépendant d’un comportement non spécifié, non défini ou défini par l’implémentation, et ne doit pas dépasser une limite d’implémentation minimale.

3) Un programme ssortingctement conforme peut utiliser des fonctions conditionnelles (voir 6.10.8.3) à condition que l’utilisation soit protégée par une directive de prétraitement d’inclusion conditionnelle appropriée utilisant la macro correspondante. …

¶7 Un programme conforme est un programme acceptable pour une implémentation conforme. 5)

5) Les programmes ssortingctement conformes sont destinés à être portables au maximum parmi les implémentations conformes. Les programmes conformes peuvent dépendre de caractéristiques non portables d’une implémentation conforme.

Je ne pense pas que la norme C comporte des caractéristiques qui, si elles sont utilisées comme prévu, rendent le programme non ssortingctement conforme. Si tel est le cas, ils sont liés au comportement dépendant de la localisation. Le comportement du code FAM n’est pas inhérent aux parameters régionaux.

Je ne pense pas que l’utilisation d’un FAM signifie insortingnsèquement que le programme n’est pas ssortingctement conforme.

question 3

Je pense que la question 3 est ambiguë entre:

  • 3A: Le décalage du membre du groupe flexible doit-il être égal à la taille de la structure contenant le membre du groupe flexible?
  • 3B: Le décalage du membre de groupe flexible doit-il être supérieur au décalage de tout membre précédent de la structure?

La réponse à 3A est “Non” (voyez l’exemple de C11 au paragraphe 25, cité ci-dessus).

La réponse à 3B est “Oui” (témoin §6.7.2.1 ¶15, cité ci-dessus).

Dissident de la réponse de Dror

Je dois citer le standard C et la réponse de Dror. J’utiliserai [DK] pour indiquer le début d’une citation tirée de la réponse de Dror, tandis que les citations non marquées sont tirées de la norme C.

À compter du 2017-07-01 18:00 – 08:00, Dror K a répondu brièvement :

[DK]

  1. Oui. Les conventions courantes d’utilisation de FAM exposent nos programmes à la possibilité d’un comportement indéfini. Cela dit, je ne suis au courant d’aucune implémentation conforme existante qui se comporterait mal.

Je ne suis pas convaincu que le simple fait d’utiliser un FAM signifie que le programme a automatiquement un comportement indéfini.

[DK]

  1. Possible, mais improbable. Même si nous n’atteignons pas réellement un comportement indéfini, nous risquons toujours d’échouer à une conformité ssortingcte.

Je ne suis pas convaincu que l’utilisation d’un FAM rend automatiquement un programme non ssortingctement conforme.

[DK]

  1. Non, il n’est pas nécessaire que l’offset du FAM soit à la fin de la structure, il peut recouvrir tous les octets de remplissage de fin.

C’est la réponse à mon interprétation 3A et je suis d’accord avec cela.

La réponse longue contient l’interprétation des réponses courtes ci-dessus.

[DK]

Le problème était que les implémentations courantes de C99, telles que GCC, ne respectaient pas l’exigence de la norme et permettaient au FAM de superposer les derniers octets de remplissage. Leur approche a été jugée plus efficace et, pour qu’ils respectent les exigences de la norme, le comité a décidé de modifier les spécifications et à partir de C99 TC2 (novembre 2004), la norme n’était plus nécessaire. l’offset du FAM doit être à la fin de la structure.

Je suis d’accord avec cette parsing.

[DK]

La nouvelle spécification supprimait l’affirmation exigeant que l’offset de FAM fût à la fin de la structure et introduisait une conséquence très regrettable, car la norme donne à l’implémentation la liberté de ne pas conserver les valeurs des octets de remplissage dans les structures ou les syndicats dans un état cohérent.

Je conviens que la nouvelle spécification a supprimé l’obligation de stocker le FAM dans un décalage supérieur ou égal à la taille de la structure.

Je ne suis pas d’accord qu’il y a un problème avec les octets de remplissage.

La norme indique explicitement que l’affectation de structure pour une structure contenant un FAM ignore effectivement le FAM (§6.7.2.1 ¶18). Il doit copier les membres non-FAM. Il est explicitement indiqué que les octets de remplissage ne doivent pas du tout être copiés (§6.2.6.1 §6 et note de bas de page 51). Et l’exemple 2 indique explicitement (de manière non normative, § 6.7.2.1 § 25) que, si la FAM recouvre l’espace défini par la structure, les données de la partie de la FAM qui chevauche la fin de la structure peuvent être ou non copié.

[DK]

Cela signifie que si l’un de nos éléments FAM correspond (ou recouvre) l’un des octets de remplissage de fin, lors de l’enregistrement dans un membre de la structure, ils peuvent (éventuellement) prendre des valeurs non spécifiées. Nous n’avons même pas besoin de nous demander si cela s’applique à une valeur stockée dans le FAM lui-même, même l’interprétation ssortingcte que cela ne s’applique qu’aux membres autres que le FAM est suffisamment préjudiciable.

Je ne vois pas cela comme un problème. Toute attente selon laquelle vous pouvez copier une structure contenant un FAM en utilisant une affectation de structure et faire copier le tableau FAM est insortingnsèquement erronée – la copie laisse les données FAM non copiées de manière logique. Tout programme dépendant des données FAM dans le cadre de la structure est interrompu; c’est une propriété du programme (imparfait), pas la norme.

[DK]

 #include  #include  #include  int main(void) { struct s { size_t len; char pad; int array[]; }; struct s *s = malloc(sizeof *s + sizeof *s->array); if (sizeof *s > offsetof(struct s, array)) { s->array[0] = 123; s->len = 1; /* any padding bytes take unspecified values */ printf("%d\n", s->array[0]); /* indeterminate value */ } free(s); return 0; } 

Idéalement, bien sûr, le code atsortingbuerait une valeur déterminée au pad membre nommé, mais cela ne pose pas de problème car il n’est jamais utilisé.

Je ne suis absolument pas d’accord pour dire que la valeur de s->array[0] dans printf() est indéterminée; sa valeur est 123 .

La citation standard antérieure est (c’est le même § 6.2.6.1 § 6 en C99 et en C11, bien que le numéro de note en bas de page soit 42 en C99 et 51 en C11):

Lorsqu’une valeur est stockée dans un object de type structure ou union, y compris dans un object membre, les octets de la représentation de l’object correspondant aux octets de remplissage prennent des valeurs non spécifiées.

Notez que s->len n’est pas une affectation à un object de type structure ou union; c’est une affectation à un object de type size_t . Je pense que cela peut être la source principale de confusion ici.

Si le code inclus:

 struct s *t = malloc(sizeof(*t) + sizeof(t->array[0])); *t = *s; printf("t->array[0] = %d\n", t->array[0]); 

alors la valeur imprimée est bien indéterminée. Cependant, c’est parce que copier une structure avec un FAM n’est pas garanti pour copier le FAM. Un code plus proche serait correct (en supposant que vous ajoutiez #include , bien sûr):

 struct s *t = malloc(sizeof(*t) + sizeof(t->array[0])); *t = *s; memmmove(t->array, s->array, sizeof(t->array[0])); printf("t->array[0] = %d\n", t->array[0]); 

Maintenant, la valeur imprimée est déterminée ( 123 ). Notez que la condition sur if (sizeof *s > offsetof(struct s, array)) est sans importance pour mon parsing.

Comme le rest de la réponse longue (principalement la section identifiée par le titre “comportement indéfini”) est basé sur une fausse inférence sur la possibilité que les octets de remplissage d’une structure changent lors de l’affectation à un membre entier d’une structure, la discussion n’a pas besoin d’une parsing plus approfondie.

[DK]

Une fois que nous avons stocké dans un membre de la structure, les octets de remplissage prennent des octets non spécifiés. Par conséquent, toute hypothèse émise sur les valeurs des éléments FAM correspondant aux octets de remplissage suivants est désormais fausse. Ce qui signifie que toute hypothèse nous conduit à échouer à une ssortingcte conformité.

Ceci est basé sur une fausse prémisse; la conclusion est fausse.

Si l’on autorise un programme ssortingctement conforme à utiliser le comportement défini par l’implémentation dans les cas où il “fonctionnerait” avec tous les comportements légitimes (même si pratiquement tout type de sortie utile dépend de détails définis par l’implémentation, tels que le jeu de caractères d’exécution ), l’utilisation d’éléments de réseau flexibles dans un programme ssortingctement conforme devrait être possible à condition que le programme ne se soucie pas de savoir si le décalage du membre de réseau flexible coïncide avec la longueur de la structure.

Les tableaux ne sont pas considérés comme ayant un remplissage interne, donc tout remplissage ajouté à cause du FAM le précéderait. S’il y a suffisamment d’espace dans ou au-delà d’une structure pour accueillir des membres dans une FAM, ces membres font partie de la FAM. Par exemple, étant donné:

 struct { long long x; char y; short z[]; } foo; 

la taille de “foo” peut être complétée au-delà du début de z raison de l’alignement, mais un tel remplissage sera utilisable dans z . L’écriture y pourrait perturber le remplissage précédant z , mais ne devrait perturber aucune partie de z elle-même.