9
votes

Où la norme C ++ décrit-elle la coulée des pointeurs à des primitives?

dans l'excellent blog post Quoi Le programmeur doit connaître le comportement non défini , la section «Règles de type violation» indique:

Il s'agit d'un comportement indéfini de jeter un int * à un flotteur * et de la déréférence qu'elle (accédant à "Int" comme s'il s'agissait d'un "flotteur"). C exige que ces sortes de conversions de type se produisent via MEMCY: l'utilisation de la coute de pointeur n'est pas correcte et des résultats de comportement non définis. Les règles pour cela sont assez nuancées et je ne veux pas aller dans les détails ici (il existe une exception pour Char *, les vecteurs ont des propriétés spéciales, des syndicats changent de choses, etc.).

J'aimerais comprendre les règles dans leur nuance complète. Où sont-ils dans la spécification C ++ 11? Ou à défaut, la spécification C (C90, C99, C11)?

dans la spécification C ++ 11 liée à partir de Cette question de dépassement de pile , N3485, je regarde 5,2.10" Reterpret Cast "mais ne vois pas la langue pour une exception pour Char * ou les syndicats. Donc ce n'est probablement pas le bon endroit. Alors où est le bon endroit?


2 commentaires

En C ++, vous devez examiner les différents types de mises dans les sections standard 5.4, des pointeurs dérivés en toute sécurité 3.7.4.3, static_cast 5.2.9, réinterpret_cast 5.2.10 ...


Serendipity: Dernière STL d'hier vidéo sur le canal 9 contient un segment sur les moules C ++ (y compris les références à la norme)


3 Réponses :


1
votes

La norme C ++ affirme que si un certain comportement n'est pas explicitement décrit à définir, il est implicitement non défini. Étant donné que la norme ne définit pas le comportement de la coulée d'un int * à float * , il est implicitement indéfini.


1 commentaires

Où dit-il que le casting à / à partir d'un char * est défini?



0
votes

reterpret_cast sur les pointeurs est défini en termes de static_cast sur vide pointeurs

5.2.10 Reinterpret Cast [expr.reinterpret.Cast]>

7 Un pointeur d'objet peut être explicitement converti en un pointeur d'objet d'un type DI FF ERENT. 70 quand une prvalue v de type "pointeur à t1" est converti en type "pointeur sur cv t2", le résultat est static_cast (static_cast (v)) si T1 et T2 sont la disposition standard types (3.9) et les exigences d'alignement de T2 ne sont pas plus strictes que ceux de T1, ou si l'un ou l'autre type est nul. Convertir une prvalue de type "Pointeur sur T1" au type "Pointeur à T2" (où T1 et T2 sont Types d'objet et où les exigences d'alignement de T2 ne sont pas plus stricte que celles de T1) et retour à son type d'origine donne le valeur de pointeur d'origine. Le résultat de tout autre pointeur de ce type La conversion n'est pas spécifiée.

5.2.9 Cast statique [expr.static.Cast]

13 Un privalue de type "pointeur sur cv1 vide" peut être converti en un privalue de type "pointeur à cv2 t," où T est un type d'objet et CV2 est la même qualification CV que, ou une grande qualification de CV plus grande que, CV1. La valeur du pointeur NULL est convertie en valeur NULL pointeur de la Type de destination. Une valeur de pointeur de type à un objet converti en "Pointeur à cv nul" et retour, éventuellement avec DI FF Erent CV-Quali fi cation, doit avoir sa valeur d'origine.


2 commentaires

Les types primitifs (char, int, flotter) sont également considérés comme des types d'objet? Ou est "objet" réservé aux classes / structs? En usage familier, les gens distinguent souvent des types primitifs et des objets.


La section 1.8 semble suggérer que les types primitifs sont en effet des objets.



4
votes

La règle que vous recherchez est au §3.10 / 10 (en C ++ 11):

Si un programme tente d'accéder à la valeur stockée d'un objet à travers une glvalue d'autre que l'un des types suivants le Le comportement n'est pas indiqué: - le type dynamique de l'objet,

- une version CV-Quali fi ée du type dynamique de l'objet,

- un type similaire (comme défini en 4,4) sur le type dynamique de l'objet,

- un type qui est le type signé ou non signé correspondant au type dynamique de l'objet, - un type de type signé ou non signé correspondant à une version CV-Quali fi ée du type dynamique de l'objet,

- un type d'agrégat ou d'union comprenant l'un des types susmentionnés parmi ses éléments ou non Membres de données (y compris, de manière récursive, un élément ou un élément de données non statique d'un sous-assemblée ou contenait union),

- Un type qui est un type de classe de base (éventuellement qualifié) du type dynamique de l'objet,

- un type de caractère ou non signé.

Il existe différents types (ou motivations) pour non défini comportement.

dans le cas de la création d'un int * à float * puis la déséroférance, il est clair que la norme ne peut pas définir Depuis ce qui pourrait arriver dépendra de l'architecture, et la valeur du int . D'autre part, le paragraphe cité est complètement faux-utilisant memcpy pour faire la conversion est également un comportement non défini, pour en grande partie les mêmes raisons.

Une des motivations du comportement non définies est de permettre aux implémentations de le définir, d'une manière qui a du sens Pour l'architecture cible, si tel existe. C'est tellement un cas. Un compilateur qui l'oblige intentionnellement à échouer est défectueux. Bien sûr, si nous supposons le complément de 32 bits 2 int et 32 ​​bits IEEE float , nous pouvons nous attendre à certaines valeurs de le int pour correspondre à la piégeage NAN, qui provoquera le programme échouer. Cela fait partie de la raison pour laquelle le comportement est indéfini; permettre à de telles choses se produira. Mais si nous sommes familier avec les détails de niveau bas du matériel, il devrait fonctionner comme prévu, fourni le compilateur peut voir le casting. Si ce n'est pas le cas, ceci est un problème QOI avec le compilateur, etc. Un compilateur doit être évité pour de tels types de travail.

comme indiqué ci-dessus, ce cas particulier, et en fait, dans tout cas qui impliquent le type punning (écrit à un membre de une union et la lecture d'un autre, par exemple), pose un problème, auquel la norme n'a pas encore trouvé de suffisance formulation. Le problème se produit car normalement, le compilateur est autorisé à supposer que les pointeurs à différents types (sauf types de caractères) ne pas alias; qu'un int * ne peut jamais pointer vers le même objet qu'un float * . Et prouver que deux pointeurs Impossible d'optimiser l'alias n'est pas important. Un compilateur qui casse le code où le pointeur coulait ou l'union est clairement visible est Il suffit de cassé, même si la norme dit que c'est un comportement indéfini. Un compilateur qui brise le code où tout ce qu'il voit sont deux pointeurs aux types non liés est compréhensible, même dans les cas où le Standard indique que le comportement est bien défini.

en utilisant memcpy évite ce problème en utilisant deux différents objets, qui ne pas alias. Il rencontre toujours indéfini comportement parce que mettre le modèle de bits d'un int dans un float , puis accédant au flotteur, n'a pas de défini comportement. (Ou vice-versa; Je connais d'au moins une machine où Copier les bits d'un float dans un int peut entraîner une Illégal int valeur.)


3 commentaires

Merci beaucoup! En ce qui concerne MemCPY, section 3.9 Par 2 dit "Pour tout objet (autre qu'un sous-box de classe de base) de type T de manière triviale t, que l'objet contienne ou non une valeur valide de type T, les octets sous-jacents (1.7) constituent l'objet. peut être copié dans un tableau de char ou non signé. "


@ Marc.martin oui. Les types de caractères sont spéciaux et ni les caractère ni non signé Char sont autorisés à avoir des valeurs de piégeage. De même, lorsque le compilateur voit un char * , il doit supposer que le pointeur peut alias tout autre type.


Je n'ai jamais vu une machine qui avait des valeurs illégales int , lequel était-ce?