34 votes

Les membres flexibles d'un tableau peuvent conduire à un comportement indéfini ?

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

  2. Est-il possible pour un programme d'utiliser les GPA et de rester un programme strictement conforme ?

  3. Le décalage de l'élément du tableau flexible doit-il être à la fin de la structure ?

Les questions s'appliquent à la fois C99 (TC3) y C11 (TC1) .

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

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

0 votes

Le C11 TC1 n'était-il pas le document qui a changé une paire de yyyymm en valeurs 201112 ? Le C11 TC1 n'a donc aucun effet sur le support FAM, etc.

0 votes

@JonathanLeffler Pour éviter la confusion, j'ai ajouté de la confusion. En spécifiant C99 TC0 je faisais référence à C99 avant tout TC, en spécifiant C99 TC2 je faisais référence à C99 après TC2, et en spécifiant C11 TC1 je faisais référence à C11 après TC1. Si vous avez une suggestion sur la façon de refléter cela d'une manière moins confuse, faites-le moi savoir. Pour l'instant, j'ajoute des parenthèses

1 votes

Q1 est une non-question. Si vous écrivez un bug dans votre programme, FAMs ou autre, il peut avoir un comportement non défini. Pouvez-vous expliquer ce que vous demandez dans Q1 (et en quoi cela diffère de votre Q2) ?

26voto

Dror K. Points 1194

La réponse courte

  1. Oui. Les conventions courantes d'utilisation des FAM exposent nos programmes à la possibilité d'un comportement non défini. Ceci dit, je n'ai pas connaissance d'une implémentation conforme existante qui se comporterait mal.

  2. Possible, mais peu probable. Même si nous n'atteignons pas réellement le comportement non défini, nous sommes toujours susceptibles d'échouer la stricte conformité.

  3. Non. Le décalage du FAM n'a pas besoin d'être à la fin de la structure, il peut recouvrir tout octet de remplissage de fin.

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


La longue réponse

Les FAMs ont été introduits pour la première fois dans C99 (TC0) (Dec 1999), et leur spécification originale exigeait que l'offset du FAM soit à la fin de la structure. La spécification originale était bien définie et, en tant que telle, ne pouvait pas conduire à un comportement non défini ou poser un problème de stricte conformité.

C99 (TC0) §6.7.2.1 p16 (Déc. 1999)

[Ce document est la norme officielle, il est protégé par des droits d'auteur et n'est pas disponible gratuitement.]

Le problème était que les implémentations courantes de C99, telles que GCC, ne suivaient pas les exigences de la norme et permettaient au FAM de recouvrir tous les octets de remplissage de fin. Leur approche a été considérée comme plus efficace, et puisque pour eux de suivre l'exigence de la norme - aurait pour résultat de briser la compatibilité rétroactive, le comité a choisi de changer la spécification, et à partir de C99 TC2 (Nov 2004) la norme n'exige plus que l'offset du FAM soit à 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 flexible du tableau était omis, sauf qu'il peut avoir plus de remplissage à la fin que ce que l'omission impliquerait.

La nouvelle spécification a supprimé la déclaration qui exigeait que le décalage du FAM soit à la fin de la structure, et elle a introduit une conséquence très malheureuse, car la norme donne à l'implémentation la liberté de ne pas conserver les valeurs des octets de remplissage dans les structures ou les unions dans un état cohérent. Plus précisément :

C99 (TC3) §6.2.6.1 p6

Lorsqu'une valeur est stockée dans un objet de type structure ou union, y compris dans un objet membre, les octets de la représentation de l'objet qui correspondent à des octets de remplissage prennent des valeurs non spécifiées.

Cela signifie que si l'un de nos éléments FAM correspond à (ou recouvre) des octets de remplissage de fin, lors du stockage dans un membre de la structure, ils peuvent 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 stricte selon laquelle cela ne s'applique qu'aux membres autres que le FAM, est suffisamment dommageable.

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

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 stockons dans un membre de la structure, les octets de remplissage prennent des octets non spécifiés, et donc toute supposition faite sur les valeurs des éléments FAM qui correspondent à tout octet de remplissage de queue, est maintenant fausse. Ce qui signifie que toute hypothèse nous fait échouer à la stricte conformité.

Comportement indéfini

Bien que les valeurs des octets de remplissage soient des "valeurs indéterminées", on ne peut pas en dire autant du type qu'elles affectent, car une représentation d'objet basée sur des valeurs indéterminées peut générer une représentation piège. Ainsi, le seul terme standard qui décrit ces deux possibilités serait "valeur indéterminée". S'il se trouve que le type du FAM a des représentations pièges, alors y accéder n'est pas seulement un souci de valeur indéterminée, mais un comportement indéfini.

Mais attendez, il y a plus. Si nous acceptons que le seul terme standard pour décrire une telle valeur est "valeur indéterminée", alors même si le type du FAM n'a pas de représentation piège, nous avons atteint un comportement non défini, puisque l'interprétation officielle du comité de normalisation du C est que passer des valeurs indéterminées aux fonctions de la bibliothèque standard est un comportement non défini.

3 votes

Il ne semble pas que le passage que vous citez ait quelque chose à voir avec la question que vous développez plus loin. C99 TC2 §6.7.2.1 p16 implique qu'étant donné struct A {char x; int y[];}; il est permis de sizeof(struct A) pour être par exemple 4 alors que l'omission de y ferait sizeof(struct A) égale 1. y ne se chevaucherait pas avec le rembourrage de fin de ligne ajouté. Par contre, étant donné struct B {int x; short y; char z[];}; le membre z peut chevaucher le rembourrage de fin de ligne struct B {int x; short y;} l'aurait fait de toute façon, avec ou sans C99 TC2 §6.7.2.1 p16. De toute façon, la C99 est remplacée par la C11 et n'est plus une norme.

7 votes

La conclusion qu'aux octets à l'offset du FAM serait toujours considéré rembourrage a besoin d'une référence IMHO. D'après ce que j'ai compris, ces octets sont remplis dans une structure qui ne contient pas le FAM, mais pas dans la structure contenant le FAM.

1 votes

@FelixPalmen Je ne vois pas de langage normatif mais l'exemple de la p25 (à partir du n1570) le suggère : La mission : *s1 = *s2; ne fait que copier le membre n ; si l'un des éléments du tableau se trouve dans le premier sizeof (struct s) de la structure, ils peuvent être copiés ou simplement remplacés par des valeurs indéterminées. .

19voto

Jonathan Leffler Points 299946

Il s'agit d'une longue réponse traitant en profondeur d'un sujet délicat.

TL;DR

Je ne suis pas d'accord avec le analyse par Dror K .

Le problème principal est une mauvaise compréhension de ce que signifie le §6.2.1 ¶6 des normes C99 et C11, et son application inappropriée à une simple affectation d'un nombre entier telle que :

fam_ptr->nonfam_member = 23;

Cette mission est no autorisé à modifier tout octet de remplissage dans la structure pointée par fam_ptr . Par conséquent, l'analyse fondée sur la présomption que cela peut modifier les octets de remplissage dans la structure est erronée.

Contexte

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

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

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

C'est le CT3, par exemple, qui a déclaré que gets() était obsolète et déprécié, ce qui a conduit à sa suppression de la norme C11.

La norme C11 - ISO/IEC 9899:2011 - comporte un corrigendum technique corrigendum technique, mais qui fixe simplement la valeur de deux macros laissées accidentellement laissées dans le formulaire 201ymmL - les valeurs requises pour __STDC_VERSION__ y __STDC_LIB_EXT1__ ont été corrigés à la valeur 201112L . (Vous pouvez voir le TC1 - formellement "ISO/IEC 9899:2011/Cor.1:2012(en) Technologies de l'information - Langages de programmation - C TECHNIQUE CORRIGENDUM 1" - à l'adresse https://www.iso.org/obp/ui/#iso:std:iso-iec:9899:ed-3:v1:cor:1:v1:en . Je n'ai pas encore trouvé comment le télécharger, mais c'est tellement simple que cela n'a pas beaucoup d'importance.

Norme C99 sur les éléments de réseaux flexibles

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

Dans un cas particulier, le dernier élément d'une structure comportant plus d'une membre nommé peut avoir un type de tableau incomplet ; on appelle cela un élément de réseau flexible . À deux exceptions près, l'élément de réseau flexible est ignoré. Tout d'abord, la taille de la structure doit être égale au décalage du dernier élément d'une structure par ailleurs identique qui remplace la structure membre de tableau flexible par un tableau de longueur non spécifiée. 106) Deuxièmement, lorsqu'un . (ou -> ) a un opérande de gauche qui est (un pointeur vers) une structure avec un membre de tableau flexible et l'opérande de droite (un pointeur vers). structure avec un membre de tableau flexible et l'opérande droit nomme ce membre. 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 ferait pas l'affaire. par le plus long tableau (avec le même type d'élément) qui ne rendrait pas la structure structure plus grande que l'objet auquel on accède ; le décalage du tableau reste celui du tableau flexible. tableau reste celui du membre du tableau flexible, même s'il est différent de celui du tableau de remplacement. même s'il diffère de celui du tableau de remplacement. Si ce tableau n'a pas d'éléments, il se comporte comme s'il avait un élément, mais le comportement est différent. élément mais le comportement est indéfini si une tentative est faite pour accéder à cet élément ou de générer un pointeur sur cet élément.

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

(Cette note de bas de page est supprimée dans la réécriture). La norme C99 originale comprenait un exemple :

¶17 EXEMPLE En supposant que tous les membres du tableau sont alignés de la même façon, 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)

ont la même valeur. La structure struct s possède 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 réussissent, les objets pointé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 d'autres affectations réussies :

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

ils se comportent alors comme si les déclarations l'é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 L'affectation :

*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 a été supprimée en C11. Le changement n'a pas été noté (et n'avait pas besoin d'être noté) dans le CT2 parce que les exemples ne sont pas normatifs. exemples ne sont pas normatifs. Mais le matériel réécrit dans la C11 est informatif lorsqu'il est étudié.

N983 document identifiant un problème avec les membres d'un réseau flexible

N983 du WG14 Pré-Santa Cruz-2002 mailing est, je crois, la déclaration initiale d'un rapport de défaut. Il indique que certains compilateurs C (en citant trois) parviennent à mettre un FAM avant le padding à la fin d'une structure. Le rapport de défaut final était DR 282 .

D'après ce que j'ai compris, ce rapport a conduit au changement dans le CT2, bien que je n'ai pas retracé toutes les étapes du processus. Il semble que le DR ne soit plus disponible séparément.

Le CT2 a utilisé la formulation de la norme C11 dans le matériel normatif.

Norme C11 sur les éléments de réseaux flexibles

Alors, que dit la norme C11 à propos des membres de réseaux flexibles ?

§6.7.2.1 Spécificateurs de structure et d'union

¶3 Une structure ou une union ne doit pas contenir un membre dont le type est incomplet ou type de fonction (par conséquent, une structure ne doit pas contenir une instance de elle-même, mais peut contenir un pointeur vers une instance d'elle-même), sauf que que le dernier membre d'une structure comportant plus d'un membre nommé peut avoir un type 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, et ceci est confirmé par :

¶15 Au sein d'un objet de structure, les membres du champ non-binaire et le les unités dans lesquelles les champs de bits résident ont des adresses qui augmentent dans ordre dans lequel ils sont déclarés.

¶17 Il peut y avoir un remplissage sans nom à la fin d'une structure ou d'une union.

¶18 A titre de cas particulier, le dernier élément d'une structure comportant plus de un membre nommé peut avoir un type de tableau incomplet. élément de réseau flexible . Dans la plupart des situations, l'élément de réseau flexible est ignoré. En particulier, la taille de la structure est la même que si le membre tableau flexible flexible était omis, sauf qu'il peut y avoir plus de remplissage en fin de ligne que que ce que l'omission impliquerait. Toutefois, lorsqu'un . (ou -> ) a un opérande gauche qui est (un pointeur vers) une structure avec un tableau flexible. structure avec un membre de tableau flexible et l'opérande droit nomme ce membre. nomme ce membre, il se comporte comme si ce membre était remplacé par le tableau le plus long (avec les mêmes éléments). par le plus long tableau (avec le même type d'élément) qui ne rendrait pas ne rendrait pas la structure plus grande que l'objet auquel on accède ; le décalage du tableau reste celui du membre du tableau flexible, même si il serait différent de celui du tableau de remplacement. Si ce tableau n'a pas d'éléments, il se comporte comme s'il en avait un. élément mais le comportement est indéfini si une tentative est faite pour accéder à cet élément ou de générer un pointeur sur cet élément.

Ce paragraphe contient la modification du ¶20 de l'ISO/IEC 9899:1999/Cor.2:2004(E) - le TC2 pour C99 ;

Les données situées à la fin de la partie principale d'une structure contenant une membre de tableau flexible est un remplissage de fin régulier qui peut se produire avec tout type de structure. Un tel remplissage ne peut pas être accédé légitimement, mais peut être passé à des fonctions de bibliothèque, etc. fonctions de bibliothèque, etc., par le biais de pointeurs vers la structure, sans comportement indéfini.

La norme C11 contient trois exemples, mais le premier et le troisième sont sont liés à des structures anonymes et à des unions plutôt qu'aux mécanismes de membres de tableaux flexibles. N'oubliez pas que les exemples ne sont pas "normatifs", mais qu'ils sont illustratifs.

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

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

la structure struct s dispose d'un membre du réseau 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 réussit, l'objet pointé par 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 notamment, les décalages des membres 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 est invalide (et viole une contrainte) car struct s est traité comme s'il ne contenait pas de membre d . L'affectation à t1.d[0] est probablement un comportement non défini, mais il est possible que

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

auquel cas l'affectation serait légitime. Néanmoins, elle ne peut apparaître dans un code strictement conforme.

¶22 Après la nouvelle déclaration :

struct ss { int n; };

les expressions :

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

sont toujours égales à 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 réussir, les objets pointés par s1 y s2 se comportent, dans la plupart des cas, comme si les identifiants avaient été déclarés comme :

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

¶24 Après d'autres affectations réussies :

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

ils se comportent alors comme si les déclarations l'é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 L'affectation :

*s1 = *s2;

ne fait que copier le membre n si l'un des éléments du tableau se trouve dans le premier sizeof (struct s) octets de la structure, ils peuvent être copiés ou simplement écrasés avec 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 objet de type structure ou union, y compris dans un objet membre, les octets de la représentation de l'objet qui correspondent à des octets de remplissage prennent des valeurs non spécifiées. valeurs non spécifiées. 51) La valeur d'une structure ou d'un objet d'union n'est n'est jamais une représentation de piège, même si la valeur d'un membre de l'objet structure ou de l'objet union peut être une représentation piège.

51) Ainsi, par exemple, l'affectation de structure ne doit pas copier aucun bit de remplissage.

Illustration de la structure problématique du GPA

Dans le C salon de discussion , I a écrit un peu de informations dont ceci est une paraphrase :

Pensez-y :

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

En supposant que le double nécessite un alignement sur 8 octets (ou 4 octets ; cela n'a pas trop d'importance n'a pas trop d'importance, mais je m'en tiendrai à 8). struct non_fam1a { double d; char c; }; aurait 7 octets de remplissage après c et une taille de 16. Plus loin, struct non_fam1b { double d; char c; char nonfam[4]; }; Le site a 3 octets de remplissage après le nonfam et une taille de 16.

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

Donc, pour un FAM suffisamment petit, la taille de la structure plus le FAM pourrait toujours être inférieure à la taille de struct fam .

L'allocation prototypique est :

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

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

Dror K. a pointé :

Il existe des macros pour calculer le stockage "précis" nécessaire. en se basant sur les offsets FAM qui sont inférieurs à la taille de la structure. Comme celle-ci : https://gustedt.wordpress.com/2011/03/14/flexible-array-member/

Répondre à la question

La question est posée :

  1. En utilisant des membres de tableaux flexibles (FAM) dans les types de structures, est-ce que nous exposons nos programmes à la possibilité d'un comportement non défini ? exposons nos programmes à la possibilité d'un comportement indéfini ?
  2. Est-il possible pour un programme d'utiliser les GPA tout en étant un programme strictement programme strictement conforme ?
  3. Le décalage du membre du tableau flexible doit-il être à la fin de la structure ? à la fin de la structure ?

Les questions s'appliquent à la fois au C99 (CT3) et au C11 (CT1).

Je pense que si vous codez correctement, les réponses sont "Non", "Oui", "Non et Oui, selon ".

Question 1

Je suppose que l'intention de la question 1 est "votre programme doit-il inévitablement être exposé à un comportement indéfini si vous utilisez n'importe quel FAM n'importe où ?" Pour énoncer ce qui me semble évident : il existe de nombreuses façons d'exposer un un programme à un comportement indéfini (et certaines de ces façons impliquent des structures avec des membres de tableaux flexibles).

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

Question 2

Section §4 Conformité définit :

¶5 A programme strictement conforme ne doit utiliser que les fonctionnalités de du langage et de la bibliothèque spécifiées dans la présente internationale. 3) Il ne doit pas produire de résultats dépendant d'un comportement non spécifié, non défini, ou défini par l'implémentation, et ne doit pas excéder un comportement minimum de l'implémentation. d'implémentation.

3) Un programme strictement conforme peut utiliser des (voir 6.10.8.3) à condition que l'utilisation soit protégée par un mécanisme de protection approprié. directive de prétraitement d'inclusion conditionnelle appropriée utilisant la macro correspondante.

¶7 A programme conforme est celle qui est acceptable pour une implémentation conforme. 5) .

5) Les programmes strictement conformes sont destinés à être portable au maximum parmi les implémentations conformes. Les programmes conformes peuvent dépendre de fonctionnalités non portables d'une implémentation conforme. d'une implémentation conforme.

Je ne pense pas qu'il y ait de caractéristiques du C standard qui, si elles sont utilisées de la manière prévue par le standard, rend le programme non strictement conforme. S'il en existe, elles sont liées à un comportement dépendant de la localisation. Le comportement du code FAM n'est pas intrinsèquement dépendant des conditions locales.

Je ne pense pas que l'utilisation d'un FAM signifie intrinsèquement que le programme n'est pas strictement conforme.

Question 3

Je pense que la question 3 est ambiguë entre :

  • 3A : Est-ce que le décalage de l'élément de tableau flexible doit être égal à la taille de la structure contenant l'élément de tableau flexible ?
  • 3B : Le décalage de l'élément du réseau flexible doit-il être supérieur à celui de tout élément antérieur de la structure ? que le décalage de tout élément antérieur de la structure ?

La réponse à la question 3A est "Non" (voir l'exemple du C11 au paragraphe 25, cité ci-dessus).

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

En désaccord avec la réponse de Dror

Je dois citer la norme C et la réponse de Dror. Je vais utiliser [DK] pour indiquer le début d'une citation de la réponse de Dror, et les citations non marquées sont tirées de la norme C.

En date du 2017-07-01 18:00 -08:00, le réponse courte par Dror K a dit :

[DK]

  1. Oui. Les conventions courantes d'utilisation des GPA exposent nos programmes à la la possibilité d'un comportement indéfini. Cela dit, je ne connais pas aucune implémentation conforme existante qui se comporterait mal.

Je ne suis pas convaincu que la simple utilisation d'un FAM signifie que le programme automatiquement un comportement indéfini.

[DK]

  1. Possible, mais peu probable. Même si nous n'atteignons pas réellement un comportement indéfini. nous sommes toujours susceptibles d'échouer la conformité stricte.

Je ne suis pas convaincu que l'utilisation d'un FAM rende automatiquement un programme non strictement conforme.

[DK]

  1. Non. L'offset du FAM n'est pas obligé d'être à la fin de la structure. il peut recouvrir tout octet 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, comme GCC, ne suivaient pas suivaient pas les exigences de la norme, et permettaient au FAM de superposer tout octet de remplissage de fin. Leur approche était considérée comme plus efficace, et puisque pour eux, suivre les exigences de la norme standard - aurait pour conséquence de rompre la compatibilité ascendante, le comité a choisi de modifier la spécification et, à partir de C99 TC2 (Nov. 2004), la norme n'exige plus l'utilisation de l'octet de remplissage. 2004) la norme n'exigeait plus que le décalage du FAM soit à à la fin de la structure.

Je suis d'accord avec cette analyse.

[DK]

La nouvelle spécification a supprimé la déclaration qui exigeait que l'offset du FAM soit à la fin de la structure. du FAM soit à la fin du struct, et cela a introduit une conséquence très malheureuse conséquence très malheureuse, car la norme donne à la mise en œuvre la liberté de ne pas garder les valeurs des octets de remplissage dans les structures structures ou unions dans un état cohérent.

Je suis d'accord que la nouvelle spécification a supprimé l'exigence que le FAM soit stocké à un décalage supérieur ou égal à la taille de la structure. structure.

Je ne pense pas qu'il y ait 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). Elle doit copier les membres non FAM. Il est explicitement indiqué que les octets de remplissage ne doivent pas être copiés du tout (§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 le GPA chevauche l'espace défini par la structure, les données de la partie du GPA qui chevauche l'espace défini par la structure ne sont pas prises en compte. de la partie du FAM qui chevauche la fin de la structure peuvent être peuvent ou non être copiées.

[DK]

Cela signifie que si l'un de nos éléments FAM correspond à (ou recouvre) des octets de remplissage de fin, lors du stockage dans un membre de la structure- ils (peuvent) 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. le FAM lui-même, même l'interprétation stricte que cela s'applique seulement aux membres autres que le FAM, est assez dommageable.

Je ne vois pas cela comme un problème. Si vous pensez que vous pouvez copier une structure contenant un FAM à l'aide de la fonction l'assignation de structure et que le tableau FAM soit copié est intrinsèquement la copie laisse les données FAM logiquement non copiées. Tout programme qui dépend des données FAM dans le cadre de la structure est cassé. structure est cassé ; c'est une propriété du programme (défectueux), pas de la norme. standard.

[DK]

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

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 devrait définir le membre nommé pad à une valeur déterminée, mais cela ne cause pas réellement de problème puisque l'on n'y accède jamais.

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

La citation de la norme antérieure est (il s'agit du même §6.2.6.1 ¶6 dans C99 et C11, bien que le numéro de la note de bas de page soit 42 dans C99 et 51 dans C11) :

Lorsqu'une valeur est stockée dans un objet de type structure ou union, y compris dans un objet membre, les octets de la représentation de l'objet qui correspondent à des octets de remplissage prennent des valeurs non spécifiées.

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

Si le code est 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 effectivement indéterminée. Cependant, c'est parce que la copie d'une structure avec un FAM n'est pas garantie de copier le FAM. Un code plus correct serait (en supposant que vous ajoutez #include <string.h> 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 (elle est 123 ). Notez que la condition sur if (sizeof *s > offsetof(struct s, array)) est sans importance pour mon analyse.

Puisque le reste de la réponse longue (principalement la section identifiée par le l'intitulé "comportement non défini") est basé sur une déduction erronée concernant la possibilité que les octets de remplissage d'une structure changent lors de l'attribution à un membre entier d'une structure, le reste de la discussion ne nécessite pas d'analyse supplémentaire. n'a pas besoin d'une analyse plus approfondie.

[DK]

Une fois que nous stockons dans un membre de la structure, les octets de remplissage prennent octets non spécifiés et, par conséquent, toute hypothèse faite sur les valeurs des éléments FAM qui correspondent aux octets de remplissage de queue, est maintenant fausse. Ce qui signifie que toute hypothèse nous fait échouer à la stricte stricte.

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

7voto

supercat Points 25534

Si l'on permet à un programme strictement conforme d'utiliser un comportement défini par l'implémentation dans les cas où il "fonctionnerait" avec tous les comportements légitimes (en dépit du fait que presque tout type de sortie utile dépendrait de détails définis par l'implémentation tels que le jeu de caractères d'exécution), l'utilisation de membres de tableaux flexibles dans un programme strictement conforme devrait être possible à condition que le programme ne se soucie pas de savoir si le décalage du membre de tableau 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 qui est ajouté à cause du FAM le précéderait. S'il y a suffisamment d'espace à l'intérieur ou à l'extérieur d'une structure pour accueillir les membres d'un FAM, ces membres font partie du 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 l'élément z en raison de l'alignement, mais tout rembourrage de ce type sera utilisable dans le cadre de l'initiative z . Rédaction y pourrait perturber le rembourrage qui précède z mais ne doit pas perturber une quelconque partie de z lui-même.

0 votes

Je suis désolé mais votre compréhension est incorrecte. La norme indique explicitement "ne doit pas produire de sortie dépendante", cela ne signifie pas qu'il n'est pas permis "d'utiliser". Le simple fait d'utiliser 'sizeof' et/ou 'offsetof' n'enfreint pas la stricte conformité. J'ai bien peur qu'en l'état actuel, votre réponse doive être déclassée.

0 votes

@DrorK. : Mieux ?

0 votes

Vous êtes plus que bienvenu pour discuter de ce sujet dans le salon de discussion C

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