web-dev-qa-db-fra.com

Pourquoi / quand utiliser `intptr_t` pour le transtypage en C?

J'ai une question concernant l'utilisation de intptr_t Par rapport à long int. J'ai observé que l'incrémentation des adresses mémoire (par exemple via l'arithmétique du pointeur manuel) diffère selon le type de données. Par exemple, l'incrémentation d'un pointeur char ajoute 1 à l'adresse mémoire, tandis que l'incrémentation d'un pointeur int ajoute 4, 8 pour un double, 16 pour un long double, etc.

Au début, j'ai fait quelque chose comme ça:

char myChar, *pChar;
float myFloat, *pFloat;

pChar = &myChar;
pFloat = &myFloat;

printf( "pChar:  %d\n", ( int )pChar );
printf( "pFloat: %d\n", ( int )pFloat );

pChar++;
pFloat++;

printf( "and then after incrementing,:\n\n" );
printf( "pChar:  %d\n", (int)pChar );
printf( "pFloat:    %d\n", (int)pFloat );

qui a compilé et exécuté très bien, mais XCode m'a donné des avertissements pour mon transtypage: "Cast du pointeur vers un entier de taille différente."

Après quelques recherches sur Google et binging (ce dernier est-il encore un mot?), J'ai vu certaines personnes recommander d'utiliser intptr_t:

#include <stdint.h>

...

printf( "pChar:  %ld\n", ( intptr_t )pChar );
printf( "pFloat: %ld\n", ( intptr_t )pFloat );

ce qui résout en effet les erreurs. Donc, je pensais qu'à partir de maintenant, je devrais utiliser intptr_t Pour les pointeurs de transtypage ... Mais après quelques agitations, j'ai trouvé que je pouvais résoudre le problème en remplaçant simplement int par long int:

printf( "pChar:  %ld\n", ( long int )pChar );
printf( "pFloat: %ld\n", ( long int )pFloat );

Ma question est donc la suivante: pourquoi intptr_t Est-il utile et quand doit-il être utilisé? Cela semble superflu dans ce cas. De toute évidence, les adresses mémoire de myChar et myFloat étaient tout simplement trop grandes pour tenir dans un int... afin de les transtyper en long int S ont résolu le problème .

Est-ce que les adresses mémoire sont parfois trop grandes pour long int Aussi? Maintenant que j'y pense, je suppose que c'est possible si vous avez> 4 Go de RAM, auquel cas les adresses mémoire pourraient dépasser 2 ^ 32 - 1 (valeur maximale pour les entrées longues non signées ...) mais C a été créé bien avant imaginable, non? Ou étaient-ils aussi prémonitoires?

Merci!

43
grisaitis

Voici la chose: sur certaines plates-formes, int est la bonne taille, mais sur d'autres, long est la bonne taille. Comment savez-vous lequel est celui que vous devez utiliser? Non. On peut avoir raison, mais la norme ne garantit pas de laquelle il s'agirait (si c'est le cas). Ainsi, la norme fournit un type défini pour être de la bonne taille, quelle que soit la plateforme sur laquelle vous vous trouvez. Où auparavant vous deviez écrire:

#ifdef PLATFORM_A
  typedef long intptr;
#else
  typedef int intptr;
#endif

Maintenant, vous écrivez:

#include <stdint.h>

Et cela couvre tellement plus de cas. Imaginez spécialiser l'extrait ci-dessus pour chaque plate-forme unique votre code s'exécute.

36
Chris Lutz

intptr_t est une nouvelle invention, créée après avoir imaginé des adresses mémoire 64 bits et même 128 bits.

Si vous jamais devez convertir un pointeur en un type entier, toujours utilisez intptr_t. Faire autre chose entraînera des problèmes inutiles pour les personnes qui ont besoin de porter votre code à l'avenir.

Il a fallu beaucoup de temps pour résoudre tous les bogues avec cela dans des programmes comme Mozilla/Firefox lorsque les gens voulaient le compiler sur Linux 64 bits.

45
Zan Lynx

Premier, intptr_t est uniquement destiné aux pointeurs de données (pas aux fonctions) et il n'est pas garanti d'exister.

Ensuite, non, vous ne devez pas l'utiliser pour l'impression. Le %p est pour ça. Il vous suffit de lancer votre pointeur sur (void*) et c'est parti.

Il n'est pas non plus utile pour l'arithmétique/l'accès aux octets individuels. Cast vers (unsigned char*) au lieu.

intptr_t est vraiment pour les rares occasions où vous devez interpréter les pointeurs comme des entiers (ce qu'ils ne sont vraiment pas). Ne fais pas ça si tu ne dois pas.

10
Jens Gustedt

Vous pourriez vous faciliter la vie en utilisant le spécificateur de conversion p:

printf("%p\n", (void *)foo);

De plus, la méthode portable pour imprimer une variable de type (u)intptr_t consiste à utiliser le PRI*PTR macros de inttypes.h; ce qui suit équivaut à utiliser p sur ma plateforme (32 bits):

printf("%08" PRIxPTR "\n", (uintptr_t)(void *)foo);

Les jets à void * sont nécessaires pour une portabilité complète, mais peuvent être omis sur les plates-formes avec des représentations de pointeurs uniformes.

9
Christoph