Sur les implémentations avec un modèle de mémoire plate (essentiellement tout), la conversion vers uintptr_t
Just Work fonctionnera.
(Mais voir Les comparaisons de pointeurs doivent-elles être signées ou non signées en 64 bits x86? Pour savoir si vous devez traiter les pointeurs comme signés ou non, y compris les problèmes de formation de pointeurs en dehors des objets qui est UB en C.)
Mais les systèmes avec des modèles de mémoire non plats existent, et de penser à leur sujet peuvent aider à expliquer la situation actuelle, comme C ++ ayant des spécifications pour <
contre std::less
.
Une partie de l'intérêt des <
pointeurs sur pour séparer les objets étant UB en C (ou du moins non spécifié dans certaines révisions C ++) est de permettre des machines étranges, y compris des modèles de mémoire non plats.
Un exemple bien connu est le mode réel x86-16 où les pointeurs sont segment: offset, formant une adresse linéaire 20 bits via (segment << 4) + offset
. La même adresse linéaire peut être représentée par plusieurs combinaisons seg: off différentes.
C ++ std::less
sur des pointeurs sur des ISA étranges peut avoir besoin d'être coûteux , par exemple "normaliser" un segment: offset sur x86-16 pour avoir un offset <= 15. Cependant, il n'y a aucun moyen portable de l'implémenter. La manipulation requise pour normaliser un uintptr_t
(ou la représentation d'objet d'un objet pointeur) est spécifique à l'implémentation.
Mais même sur des systèmes où C ++ std::less
doit être coûteux, <
cela ne doit pas l'être. Par exemple, en supposant un "grand" modèle de mémoire où un objet tient dans un segment, <
il suffit de comparer la partie décalée et de ne pas même déranger avec la partie de segment. (Les pointeurs à l'intérieur du même objet auront le même segment, et sinon c'est UB en C. C ++ 17 est devenu simplement "non spécifié", ce qui pourrait encore permettre de sauter la normalisation et de comparer simplement les décalages.) Cela suppose que tous les pointeurs de n'importe quelle partie d'un objet utilise toujours la même seg
valeur, ne se normalisant jamais. C'est ce que vous attendez d'un ABI pour un modèle de mémoire "grand" par opposition à "énorme". (Voir la discussion dans les commentaires ).
(Un tel modèle de mémoire peut avoir une taille d'objet maximale de 64 Ko par exemple, mais un espace d'adressage total maximal beaucoup plus grand qui a de la place pour de nombreux objets de cette taille maximale. ISO C permet aux implémentations d'avoir une limite de taille d'objet inférieure à la la valeur maximale (non signée) size_t
peut représenter ,. SIZE_MAX
Par exemple, même sur les systèmes de modèle à mémoire plate, GNU C limite la taille maximale de l'objet PTRDIFF_MAX
pour que le calcul de la taille puisse ignorer le débordement signé.) Voir cette réponse et discussion dans les commentaires.
Si vous voulez autoriser des objets plus grands qu'un segment, vous avez besoin d'un "énorme" modèle de mémoire qui doit se soucier de déborder la partie décalée d'un pointeur lorsque vous effectuez p++
une boucle dans un tableau, ou lorsque vous effectuez une indexation / arithmétique de pointeur. Cela conduit à un code plus lent partout, mais cela signifierait probablement que cela p < q
se produirait pour les pointeurs vers différents objets, car une implémentation ciblant un modèle de mémoire "énorme" choisirait normalement de garder tous les pointeurs normalisés tout le temps. Voir Quels sont les pointeurs proches, lointains et énormes? - certains compilateurs C réels pour le mode réel x86 avaient une option à compiler pour le modèle "énorme" où tous les pointeurs par défaut étaient "énormes" sauf indication contraire.
La segmentation en mode réel x86 n'est pas le seul modèle de mémoire non plate possible , c'est simplement un exemple concret utile pour illustrer comment il est géré par les implémentations C / C ++. Dans la vie réelle, les implémentations ont étendu ISO C avec le concept de pointeurs far
vs. near
, permettant aux programmeurs de choisir quand ils peuvent s'en tirer en stockant / passant simplement la partie offset 16 bits, par rapport à certains segments de données courants.
Mais une implémentation ISO C pure devrait choisir entre un petit modèle de mémoire (tout sauf le code dans le même 64 Ko avec des pointeurs 16 bits) ou grand ou énorme avec tous les pointeurs étant 32 bits. Certaines boucles pouvaient être optimisées en incrémentant uniquement la partie décalée, mais les objets pointeurs ne pouvaient pas être optimisés pour être plus petits.
Si vous saviez quelle était la manipulation magique pour une implémentation donnée, vous pourriez l'implémenter en C pur . Le problème est que différents systèmes utilisent un adressage différent et les détails ne sont paramétrés par aucune macro portable.
Ou peut-être pas: cela peut impliquer de rechercher quelque chose à partir d'une table de segments spéciale ou quelque chose, par exemple comme le mode protégé x86 au lieu du mode réel où la partie segment de l'adresse est un index, pas une valeur à déplacer à gauche. Vous pouvez configurer des segments qui se chevauchent partiellement en mode protégé, et les parties de sélecteur de segment des adresses ne seraient même pas nécessairement ordonnées dans le même ordre que les adresses de base de segment correspondantes. Obtenir une adresse linéaire à partir d'un pointeur seg: off en mode protégé x86 peut impliquer un appel système, si le GDT et / ou le LDT ne sont pas mappés en pages lisibles dans votre processus.
(Bien sûr, les systèmes d'exploitation traditionnels pour x86 utilisent un modèle de mémoire plate, la base de segment est donc toujours 0 (sauf pour le stockage local par thread utilisant fs
ou gs
segments), et seule la partie "offset" 32 bits ou 64 bits est utilisée comme pointeur .)
Vous pouvez ajouter manuellement du code pour diverses plates-formes spécifiques, par exemple supposer par défaut plat, ou #ifdef
quelque chose pour détecter le mode réel x86 et le diviser uintptr_t
en moitiés 16 bits pour seg -= off>>4; off &= 0xf;
ensuite combiner ces parties en un nombre 32 bits.