Quel est le type de données uintptr_t

Réponses:

199

uintptr_test un type entier non signé capable de stocker un pointeur de données. Ce qui signifie généralement que c'est la même taille qu'un pointeur.

Il est éventuellement défini dans les normes C ++ 11 et ultérieures.

Une raison courante de vouloir un type entier pouvant contenir le type de pointeur d'une architecture est d'effectuer des opérations spécifiques à un entier sur un pointeur, ou d'obscurcir le type d'un pointeur en le fournissant comme un "handle" entier.

Edit: Notez que Steve Jessop a quelques détails supplémentaires très intéressants (que je ne volerai pas) dans une autre réponse ici pour vous les types pédants :)

Drew Dormann
la source
53
Notez que cela size_tne doit être suffisant que pour contenir la taille du plus grand objet et peut être plus petit qu'un pointeur. Cela serait attendu sur des architectures segmentées comme le 8086 (16 bits size_t, mais 32 bits void*)
MSalters
3
pour représenter la différence entre deux pointeurs, vous avez ptrdiff_t. uintptr_tn'est pas fait pour ça.
jalf
3
@jalf: Pour la différence, oui, mais pour la distance , vous voudriez un type non signé.
Drew Dormann
La définition de uintptr_t n'est apparemment pas obligatoire (donc pas standard) même en C ++ 11! cplusplus.com/reference/cstdint (j'ai eu l'astuce de la réponse de Steve Jessop)
Antonio
2
@MarcusJ unsigned intn'est généralement pas assez grand. Mais il pourrait être assez grand. Ce type existe spécifiquement pour supprimer tous les "présupposés" .
Drew Dormann
239

La première chose, au moment où la question a été posée, uintptr_tn'était pas en C ++. C'est en C99, en <stdint.h>, comme type facultatif. De nombreux compilateurs C ++ 03 fournissent ce fichier. C'est aussi en C ++ 11, en <cstdint>, où là encore c'est facultatif, et qui fait référence à C99 pour la définition.

Dans C99, il est défini comme "un type entier non signé avec la propriété que tout pointeur valide vers void peut être converti en ce type, puis reconverti en pointeur vers void, et le résultat sera comparable à celui du pointeur d'origine".

Prenez cela pour signifier ce qu'il dit. Cela ne dit rien sur la taille.

uintptr_tpeut avoir la même taille qu'un fichier void*. Il pourrait être plus grand. Il pourrait en théorie être plus petit, bien qu'une telle implémentation C ++ approche de façon perverse. Par exemple, sur une plate-forme hypothétique où void*32 bits, mais seulement 24 bits d'espace d'adressage virtuel sont utilisés, vous pourriez avoir un 24 bits uintptr_tqui satisfait à l'exigence. Je ne sais pas pourquoi une implémentation ferait cela, mais la norme le permet.

Steve Jessop
la source
8
Merci pour le "<stdint.h>". Mon application n'a pas compilé à cause de la déclaration uintptr_t. Mais quand je lis votre commentaire, j'ajoute "#include <stdint.h>" et oui maintenant ça marche. Merci!
JavaRunner
2
Pour allouer de la mémoire alignée , entre autres utilisations, par exemple?
legends2k
4
Un autre cas d'utilisation courant de la conversion d'un pointeur en un int est de créer une poignée opaque qui masque le pointeur. Cela est utile pour renvoyer une référence à un objet à partir d'API où vous souhaitez garder l'objet privé à la bibliothèque et empêcher les applications d'y accéder. L'application est ensuite obligée d'utiliser l'API pour effectuer des opérations sur l'objet
Joel Cunningham
3
@JoelCunningham: cela fonctionne mais n'est pas vraiment différent de l'utilisation void*. Cependant, cela affecte les directions futures possibles, surtout si vous souhaitez changer pour utiliser quelque chose qui n'est vraiment qu'un handle entier, pas du tout un pointeur converti.
Steve Jessop
2
@CiroSantilli 烏坎 事件 2016 六四 事件 法轮功 Un cas d'utilisation courant consiste à passer juste un entier à une API qui attend un vide * aux données génériques. Vous enregistre les frappes typedef struct { int whyAmIDoingThis; } SeriouslyTooLong; SeriouslyTooLong whyAmNotDoneYet; whyAmINotDoneYet.whyAmIDoingThis = val; callback.dataPtr = &whyAmINotDoneYet;. Au lieu de cela: callback.dataPtr = (void*)val. De l'autre côté, vous obtenez bien sûr void*et devez le lancer à nouveau int.
Francesco Dondi
19

Il s'agit d'un type entier non signé exactement de la taille d'un pointeur. Chaque fois que vous devez faire quelque chose d'inhabituel avec un pointeur - comme par exemple inverser tous les bits (ne demandez pas pourquoi) vous le transformez uintptr_tet le manipulez comme un nombre entier habituel, puis rétrogradez.

acéré
la source
6
Bien sûr, vous pourriez le faire, mais ce serait bien sûr un comportement indéfini. Je crois que la seule chose que vous pouvez faire avec le résultat d'un cast vers uintptr_t est de le transmettre tel quel et de le restituer - tout le reste est UB.
sleske
Il y a des moments où vous devez jouer avec les bits, et cela générerait normalement des erreurs de compilation. Un exemple courant est l'application de la mémoire alignée sur 16 octets pour certaines applications vidéo et critiques pour les performances. stackoverflow.com/questions/227897/…
Cloud
3
@sleske ce n'est pas vrai. Sur les machines qui ont des types auto-alignés, les deux bits les moins significatifs d'un pointeur vont être nuls (car les adresses sont des multiples de 4 ou 8). J'ai vu des programmes qui exploitent cela pour compresser les données ..
saadtaame
3
@saadtaame: Je viens de le souligner que c'est UB selon la norme C . Cela ne signifie pas qu'il ne peut pas être défini sur certains systèmes - les compilateurs et les runtimes sont libres de définir un comportement spécifique pour quelque chose qui est UB dans la norme C. Il n'y a donc pas de contradiction :-).
sleske
2
Ce n'est pas nécessairement exactement la taille d'un pointeur. Toutes les garanties standard sont que la conversion d'une void*valeur de pointeur vers uintptr_tet en arrière produit une void*valeur comparable au pointeur d'origine. uintptr_test généralement de la même taille que void*, mais cela n'est pas garanti, et il n'y a aucune garantie que les bits de la valeur convertie ont une signification particulière. Et il n'y a aucune garantie qu'il puisse contenir une valeur de pointeur vers fonction convertie sans perte d'informations. Enfin, il n'est pas garanti d'exister.
Keith Thompson
15

Il existe déjà de nombreuses bonnes réponses à la partie "qu'est-ce que le type de données uintptr_t". Je vais essayer de répondre à la question "à quoi cela peut-il servir?" partie à ce poste.

Principalement pour les opérations au niveau du bit sur les pointeurs. N'oubliez pas qu'en C ++, on ne peut pas effectuer d'opérations au niveau du bit sur des pointeurs. Pour des raisons, voir Pourquoi ne pouvez-vous pas effectuer des opérations au niveau du bit sur un pointeur en C, et existe-t-il un moyen de contourner cela?

Ainsi, pour effectuer des opérations au niveau du bit sur des pointeurs, il faudrait transtyper des pointeurs pour taper unitpr_t, puis effectuer des opérations au niveau du bit.

Voici un exemple d'une fonction que je viens d'écrire pour faire exclusif au niveau du bit ou de 2 pointeurs à stocker dans une liste liée XOR afin que nous puissions parcourir dans les deux sens comme une liste doublement liée mais sans pénalité de stocker 2 pointeurs dans chaque nœud .

 template <typename T>
 T* xor_ptrs(T* t1, T* t2)
 {
     return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(t1)^reinterpret_cast<uintptr_t>(t2));
  }
BGR
la source
1
autre que l'arithmétique des bits, c'est aussi bien si vous voulez avoir une sémantique basée sur les adresses plutôt que sur le nombre d'objets.
Alex
4

Risquant d'obtenir un autre badge Necromancer, je voudrais en ajouter un très bon usage pour uintptr_t (ou même intptr_t) et qui est d'écrire du code embarqué testable. J'écris principalement du code embarqué destiné à différents processeurs bras et actuellement tensilica. Ceux-ci ont différentes largeurs de bus natives et le tensilica est en fait une architecture Harvard avec des bus de code et de données séparés qui peuvent être de différentes largeurs. J'utilise un style de développement piloté par les tests pour une grande partie de mon code, ce qui signifie que je fais des tests unitaires pour toutes les unités de code que j'écris. Les tests unitaires sur le matériel cible réel sont un problème, donc j'écris généralement tout sur un PC basé sur Intel sous Windows ou Linux en utilisant Ceedling et GCC. Cela étant dit, beaucoup de code intégré implique des manipulations de bits et des manipulations d'adresses. La plupart de mes machines Intel sont en 64 bits. Donc, si vous allez tester le code de manipulation d'adresse, vous avez besoin d'un objet généralisé pour faire des calculs. Ainsi, uintptr_t vous offre un moyen indépendant de la machine de déboguer votre code avant d'essayer de déployer sur le matériel cible. Un autre problème est pour certaines machines ou même les modèles de mémoire sur certains compilateurs, les pointeurs de fonction et les pointeurs de données sont de largeurs différentes. Sur ces machines, le compilateur peut même ne pas autoriser le transtypage entre les deux classes, mais uintptr_t devrait être capable de contenir l'une ou l'autre.

bd2357
la source
Je ne sais pas si c'est pertinent ... Avez-vous essayé des "typedefs opaques"? Vide: youtube.com/watch?v=jLdSjh8oqmE
shycha
@sleske Je souhaite que ce soit disponible en C. Mais avoir stdint.h est mieux que rien. (Souhaitez également des énumérations où le typage est plus fort mais la plupart des débogueurs font un bon travail pour les comprendre de toute façon)
bd2357