7
votes

Différence du pointeur à travers les membres d'une structure?

Le standard de C99 indique que:

Lorsque deux pointeurs sont soustraits, les deux doivent pointer vers les éléments du même objet de tableau, ou un autre élément du dernier élément de l'objet réseau p> blockQuote>

Considérez le code suivant: P>

struct test {
    int x[5];
    char something;
    short y[5];
};

...

struct test s = { ... };
char *p = (char *) s.x;
char *q = (char *) s.y;
printf("%td\n", q - p);


5 commentaires

Et dans votre exemple, le pointeur arithmétique sur le vide est interdit de toute façon. Si les types ne sont pas les mêmes, comment pouvez-vous les soustraire?


Vous êtes correct que l'arithmétique sur des pointeurs vide n'est pas autorisé par la norme et c'est une extension GNU. Imaginez que les deux pointeurs soient char * .


GCC permet à l'arithmétique du pointeur sur Void * en traitant Tailleof (VOID) comme 1 (identique à un char * ). Donc, pour les besoins de votre question, cela ne fait aucune différence.


Ceci est nécessaire pour permettre aux implémentations de vérification des bornes, afaik, l'intention du comité standard (ou au moins était pour C89) de le permettre. Je pense qu'un contrôle de la mise en œuvre des limites peut (doit) attraper ce cas (c'est-à-dire que c'est UB, bien que cela fonctionne en réalité). Une telle mise en œuvre briserait cependant beaucoup de code existant. Et la norme est un peu vague sur sa notion d'un objet qui rend difficile la réponse exacte.


@MAFSO: En l'absence de règles d'aliasing, chaque objet pourrait être considéré comme membre d'une union contenant un membre de chaque type pouvant occuper l'espace. Donné struct {int x, y;} foo; , si pour une valeur entière n , l'adresse de foo.y serait égal ((int *) & foo) + n , puis & foo.x et & foo.y serait les adresses des éléments 0 et n < / Code> d'un réseau d'entiers qui commencent à l'adresse & foo . Malheureusement, les auteurs du jambon standard ont lancé des règles d'aliasing qui dépendent des détails des "objets" qu'ils n'ont jamais définis parce que la langue n'était pas nécessaire.


5 Réponses :


1
votes

Oui, vous êtes autorisé à effectuer le pointeur arithmétrique sur structure octets:

N1570 - 6.3.2.3 Pointeurs P7:

... Quand un pointeur sur un objet est converti en un pointeur sur un type de caractère, Le résultat pointe vers l'octet adressé le plus bas de l'objet. incréments successifs de la Résultat , jusqu'à la taille de l'objet, rendez-vous des pointeurs aux octets restants de l'objet.

Cela signifie que pour le programmeur, les octets de la conducture doivent être considérés comme une zone continue, quelle que soit la manière dont il a peut-être été mis en œuvre dans le matériel.

pas avec Void * des pointeurs, c'est-à-dire une extension de compilateur non standard. Comme mentionné sur le paragraphe de la norme, il s'applique uniquement aux pointeurs de type de caractères.

EDIT:

Comme la MAFSO a souligné dans des commentaires, ci-dessus n'est que vrai tant que le type de résultat de soustraction pTRDIFF_T a suffisamment de portée pour le résultat. Depuis la plage de Taille_T peut être plus grande que PTRIFF_T et si la structure est assez grande, il est possible que les adresses soient trop éloignées.

À cause de cela, il est préférable d'utiliser offsetof macro sur les membres de la structure et calculer le résultat de ceux-ci.


16 commentaires

+1, je crois aussi que le mot "éléments" dans la règle que citée est utilisé pour distinguer des pointeurs de type char * et des pointeurs correctement alignés du type correspondant aux éléments de réseau.


Cette réponse semble impliquer que le rembourrage pourrait être considéré comme un objet que je doute fortement était prévu.


Cette réponse manque le point. 1. Si p + n se compare égal à q , cela n'implique pas que q-p est défini. 2. L'important est, quel est l'objet ici (toute la structure ou juste le membre). J'ai tendance à interpréter la norme d'une manière en disant ce dernier.


@Shafikyaghmour Cette clause de la norme semble seulement couvrir le fait que vous pouvez obtenir un pointeur valide de cette façon. Qu'il soit ou non possible de dréérence, ledit pointeur est couvert ailleurs dans la norme.


@mafso dis-tu que le résultat de (char *) & s + offsetof (S, S.Y) == q - p n'est pas nécessairement vrai? Les règles du pointeur arithmétrique sont plutôt strictes et je ne vois pas comment la mise en œuvre pourrait le faire sans casser les règles.


PTRIFF_T est autorisé à être un moyen plus petit que Taille_T (la norme ne dit rien de leur relation), il est donc possible que la différence est simplement indéfinie (si < Code> QP est défini, le résultat est comme prévu). (J'ai a posé une question à ce sujet il y a quelque temps.) Pour la structure inférieure à 2 ** 15, ceci est toujours défini, et ce n'est pas vraiment lié au problème ici, mon point était que l'implication "si p + n est défini, de même que p + nn "n'est pas vrai (supposé silencieusement dans votre message).


@MAFSO Vous avez raison sur possible pTRDIFF_T Overflow, j'ai édité la réponse pour accueillir cela.


Est un pointeur pour rembourrer un pointeur valide? Pouvez-vous justifier cette affirmation?


Comme je l'ai dit, le problème ptrdiff_t n'est pas vraiment lié, il n'était qu'un contre-exemple à la mauvaise implication " p + n défini => p + nn défini ", que vous supposez toujours.


@Shafikyaghmour paragraphe I CITATED dit que vous obtiendrez des pointeurs qui restaient des octets de l'objet jusqu'à la taille de l'objet . Et Tailleof (anyobject) est égal à la taille de l'objet, y compris le remplissage. Donc, pour moi, il semble assez clair que le pointeur est valable en ce sens qu'il s'agit d'une adresse valide pour le pointeur arithmétrique. Pas en sens que ce serait nécessairement sûr de la désarence.


@MAFSO Qu'essayez-vous de dire? Si nous gardons dans les limites de nos types, p + n-n est bien défini.


@ user694733: Bien sûr, il est bien défini (si dans les limites ou p + n était valide (et réellement utilisé!)), mais c'est le point réel ici (et défini dans 6.5.6 P9). Et il n'est défini que pour les pointeurs dans le même objet de tableau. Et c'est la question ici: quel est l'objet? Le membre (un pointeur sur lequel est converti en char * et auquel cas la différence est UB) ou la structure contenant (ce qui répond à une interprétation courante, mais n'est pas défini dans la norme, dans la mesure où Je sais)?


@MAFSO Mon interprétation est que cet objet est la structure contenant. 3.15 décrit l'objet comme "région de stockage de données ..." "... content des valeurs" . La structure convient à cette description. De plus, les pointeurs d'origine des tableaux sont gagnés à travers la structure (avec syntaxe s.x ). Si nous prenions Char * Pointeur de S , le 6.5.6P7 indique que le PTR à l'objet peut être traité comme PTR au premier membre de la matrice. 6.3.2.3P7 garantit que, à des fins de pointeur arithmétrique, cette zone peut être considérée comme continue. En d'autres termes, vous pouvez traiter la région de la mémoire de la structure en tant que réseau d'octets. ...


@mafso ... Donc, il semble n'y avoir aucune idée que 6.3.2.3P8 et P9 ne pouvaient pas être appliqués dans ce cas. Bien sûr, si x et y étaient des matrices distinctes, cela ne fonctionnerait bien sûr pas, mais comme ils sont contenus dans la même structure, les chapitres précédemment mentionnés donnent la garantie sécurisée. La norme ne mentionne pas explicitement cette affaire comme étant UB (qui n'est pas une grande partie de quarantage), il n'ya donc pas de preuves pour prouver le contraire. Désolé pour le mur de texte :)


La notion d'objet est également intéressante pour les règles d'aliasing, voir par ex. Cette question sur restreint . Et, comme je l'ai mentionné ci-dessus, une implémentation de vérification des limites est le problème suivant (voir par exemple ici et ). Une discussion ultérieure est probablement meilleure dans le chat.


@MAfso I Je n'ai enflammé que dans les liens, mais de la réponse et de leurs commentaires, il semblerait que la vérification des limites entre en jeu dans la phase de déséroférence. En tout cas, je vais en savoir plus à ce sujet et vous avez raison; Une discussion plus approfondie devrait être sur le chat. Laissons ceci pour l'instant. Heureusement, je n'ai pas eu et n'aurez pas dans un avenir prévisible, je dois accéder à des structures autres que le moyen sûr habituel, donc je ne suis pas pressé de résoudre ce problème. :)



0
votes

Je dois signaler les éléments suivants:

de la norme C99, section 6.7.2.1:

dans un objet de structure, les membres non-bits et les unités dans lesquels des champs de bits résider a des adresses qui augmentent dans l'ordre dans lequel ils sont déclarés. Un pointeur à un objet de structure, convenablement converti, pointe vers son membre initial (ou si ce membre est un champ de bits, puis à l'unité dans laquelle il réside), et vice versa. il peut y avoir Sans nom rembourrage dans un objet de structure, mais pas à son début.

Ce n'est pas tellement que le résultat de la soustraction du pointeur entre les membres n'est pas défini tant qu'il n'est pas fiable (c'est-à-dire non garanti d'être identiques entre différentes instances du même type de structure lorsque la même arithmétique est appliquée). < / p>


0 commentaires

2
votes

L'arithmétique du pointeur nécessite que les deux pointeurs soient ajoutés ou soustraits pour faire partie du même objet car il n'a pas de sens autrement. La section citée de la norme désigne spécifiquement deux objets non liés tels que int A [b]; code> et int b [5] code>. L'arithmétique du pointeur nécessite de connaître le type de l'objet que les pointeurs pointant vers (je suis sûr que vous en êtes déjà conscient).

IE P>

printf("%zu\n", offsetof(struct test, y) - offsetof(struct test, x));


5 commentaires

Bonne réponse, mais vous oubliez que cela est autorisé lorsque les pointeurs sont tous les deux de type char * et pointez dans le même objet. Sans cela, il est impossible de définir offsetof .


Bien sûr. Mais je ne suis pas sûr d'où je contredis cela ou implique?


Vous ne le contrariez pas directement, mais c'est une "échappatoire" importante de mentionner, IMHO.


TBH, j'ai lutté de l'endroit où je pouvais l'inclure logiquement autre que comme un fait disjoint après votre commentaire. Édité. Merci.


Opérateurs arithmétiques et relationnels du pointeur ( << / code> <= > > = ) entre les pointeurs d'objets distincts ne < Je> nécessairement pas de sens. La langue pourrait aurait rendu le résultat indéterminé plutôt que non défini le comportement et l'obligeait à se comporter de manière cohérente (de sorte que & x <& y &&& <& z implique & x <& z , et ainsi de suite). Et sur de nombreux systèmes, cela fonctionne réellement de cette façon. La norme a formulé de telles opérations non définies car elles peuvent être difficiles à mettre en œuvre de manière cohérente sur certaines architectures et que cet effort de mise en œuvre supplémentaire ne vous achèterait pas particulièrement utile.



1
votes

Je crois que la réponse à cette question est plus simple qu'elle ne l'apparaît, l'OP demande:

Mais pourquoi ce résultat devrait-il être "indéfini"?

Eh bien, voyons que la définition du comportement non défini est dans le projet de section standard de C99 3.4.3 :

comportement, lors de l'utilisation d'une construction de programme non-sport ou erronée ou de données erronées, pour lesquelles cette norme internationale impose non Exigences

Il s'agit simplement de comportement pour lequel la norme n'impose pas une exigence, ce qui correspond parfaitement à cette situation, les résultats vont varier en fonction de l'architecture et tenter de spécifier les résultats auraient probablement été difficiles sinon impossibles dans un portable manière. Cela laisse la question, pourquoi choisiraient-ils un comportement indéfini par opposition à la mise en œuvre du comportement non précisé?

Très probablement, il a été fait un comportement indéfini de limiter le nombre de façons un pointeur non valide pourrait être créé, cela correspond au fait que nous sommes fournis avec offset de à Retirez le besoin potentiel de la soustraction de pointeur d'objets non liés.

Bien que la norme ne définisse pas vraiment le terme pointeur non valide, nous obtenons une bonne description dans Justification pour les langages de programmation standard internationaux-C , qui dans la section 6.3.2.3 les pointeurs dit ( emphasis. / em>):

Implicity dans la norme est la notion de pointeurs non valides. Dans discuter des pointeurs, la norme se réfère généralement à "un pointeur à un objet "ou" un pointeur à une fonction "ou" un pointeur null ". Une spéciale étui dans l'adresse arithmétique permet à un pointeur juste après la fin d'un tableau. tout autre pointeur est invalide.

La justification de C99 ajoute:

quel que soit la création d'un pointeur non valide, toute utilisation des rendements informatiques comportement non défini . Même affectation, comparaison avec un pointeur NULL constante ou comparaison avec elle-même, pourrait sur certains systèmes entraîner une exception.

Cela me suggère fortement qu'un pointeur sur le rembourrage serait un pointeur , bien qu'il soit difficile de prouver que le rembourrage n'est pas Un objet , la définition de objet dit:

Région de stockage de données dans l'environnement d'exécution, le contenu de qui peut représenter des valeurs

et notes:

Lorsqu'il est référencé, un objet peut être interprété comme ayant un particulier taper; Voir 6.3.2.1.

Je ne vois pas comment nous pouvons raisonner sur le type ou la la valeur de remplissage entre éléments d'une structure et donc ils ne sont donc pas objets ou au moins est fortement indique que le rembourrage n'est pas censé être considéré comme un objet .


1 commentaires

Je ne vois pas comment le pointeur du pointeur peut être un pointeur invalide. Le rembourrage n'est pas un objet, mais une partie d'un objet. Après tout, standard garantit que le remplissage existera, seule la valeur de celui-ci n'est pas spécifiée (6.2.6.1P1). Voir Keith Thompson Réponse .



3
votes

Les opérateurs de soustraction et de relation relationnels (sur type Char * ) entre les adresses de membre de la même structure sont bien définies.

n'importe quel objet peut être traité comme une matrice de non signé char .

citant N1570 6.2.6.1 Paragraphe 4:

valeurs stockées dans des objets de champ non bits de tout autre type d'objet consister en n × char_bit bits, où n est la taille d'un objet de cette type, en octets. La valeur peut être copiée dans un objet de type non signé Char [ n ] (par exemple, par memcpy ); L'ensemble d'octets résultant est appelé la représentation objet de la valeur.

...

Mes seuls suspicions sont des architectures de mémoire segmentées où les membres pourrait se retrouver dans différents segments. Est-ce le cas?

non. Pour un système avec une architecture de mémoire segmentée, le compilateur imposera normalement une restriction que chaque objet doit s'intégrer dans un seul segment. Ou cela peut permettre des objets qui occupent plusieurs segments, mais il doit toujours garantir que les arithmétiques du pointeur et les comparaisons fonctionnent correctement.


8 commentaires

+1 Je pense que la réponse parvient à mieux comprendre que ma réponse.


Je ne suis pas convaincu. Si un pointeur dans un objet pourrait toujours être traité comme un pointeur dans l'objet enfermé la plus à l'extérieur, par exemple le piratage de la structure serait également légal ...


@MAFSO bien, en standard, est entrée dans l'index: struct piratage, voir membre flexible de tableau ...


@ user694733: Les membres de la matrice flexibles ont été ajoutés à la norme en 1999 en tant que remplacement pour le piratage de la structure, qui était de légalité discutable.


@Keiththompson je sais. Je viens de devoir. :)


@MAFSO: C'est dommage que les matrices de taille zéro standard font une violation de contrainte. Si cela avait dit à la place d'un tableau de taille zéro dans une structure insertion de remplissage nécessaire pour forcer l'alignement approprié, puis produit l'adresse résultante sans rien attribuer, cela aurait offert une sémantique plus utile que les membres de la matrice flexibles, mais sur la plupart des implémentations. ne coûterait rien (changer simplement le tableau minimum de 1 à 0). Le piratage de la structure pourrait alors être justifié en disant que l'indice maximum pour un tableau, dans une allocation suffisante, est (LEN- (Taille_T) 1).


@supercat: Nous avons plutôt des membres de tableau flexibles. Ils travaillent. Faites avec.


@Keiththompson: ils ne fonctionnent que pour des objets de durée allouée; Il n'y a pas de moyen standard de créer une structure de durée statique ou automatique compatible avec une fonction qui s'attend à une structure avec une FAM.