Comment dois-je gérer la conversion de «void *» en «int» perd sa précision »lors de la compilation de code 32 bits sur une machine 64 bits?
J'ai un package qui compile et fonctionne correctement sur une machine 32 bits. J'essaie maintenant de le compiler sur une machine 64 bits et de trouver l'erreur suivante:
error: cast from ‘void*’ to ‘int’ loses precision
Existe-t-il un indicateur de compilation pour supprimer ces erreurs? ou dois-je modifier manuellement ces fichiers pour éviter ces conversions?
Le problème est que, en 32 bits, un int (qui est un entier 32 bits) contiendra une valeur de pointeur.
Lorsque vous passez à 64 bits, vous ne pouvez plus stocker un pointeur dans un int - il n'est pas assez grand pour contenir un pointeur 64 bits. Le type intptr_t est conçu pour cela.
Votre code est cassé. Il ne sera pas moins cassé en ignorant les avertissements que le compilateur vous donne.
Que pensez-vous pensez-vous se produira lorsque vous essayez de stocker un pointeur de 64 bits de large dans un entier 32 bits? La moitié de vos données seront jetées. Je ne peux pas imaginer de nombreux cas où c'est la bonne chose à faire, ou où cela ne causera pas d'erreurs.
Corrigez votre code. Ou restez sur la plate-forme 32 bits sur laquelle le code fonctionne actuellement.
Si votre compilateur définit intptr_t
ou uintptr_t
, utilisez-les, car ce sont des types entiers garantis suffisamment grands pour stocker un pointeur.
Si ces types ne sont pas disponibles, size_t
ou ptrdiff_t
sont également assez grands pour contenir un pointeur sur la plupart (pas toutes) des plateformes. Ou utilisez long
(généralement 64 bits sur les plates-formes 64 bits du compilateur GCC) ou long long
(un type C99 que la plupart, mais pas tous les compilateurs, prennent en charge en C++), ou un autre type intégral défini par l'implémentation qui a une largeur d'au moins 64 bits sur une plate-forme 64 bits.
Je suppose que la situation de OP est un void * est utilisé comme stockage général pour un int, où le void * est plus grand que l'int. Donc par exemple:
int i = 123;
void *v = (void*)i; // 64bit void* being (ab)used to store 32bit value
[..]
int i2 = (int)v; // we want our 32bits of the 64bit void* back
Le compilateur n'aime pas cette dernière ligne.
Je ne vais pas peser sur le bien ou le mal d'abuser d'un vide * de cette façon. Si vous voulez vraiment tromper le compilateur, la technique suivante semble fonctionner, même avec -Wall:
int i2 = *((int*)&v);
Ici, il prend l'adresse de v, convertit l'adresse en un pointeur du type de données souhaité, puis suit le pointeur.
C'est une erreur pour une raison: int
n'est que la moitié de la taille de void*
sur votre machine, vous ne pouvez donc pas simplement stocker un void*
dans un int
. Vous perdriez la moitié du pointeur et lorsque le programme tentera ultérieurement d'extraire le pointeur de ce int
, il n'obtiendra rien d'utile.
Même si le compilateur ne donnait pas d'erreur, le code ne fonctionnerait probablement pas. Le code doit être modifié et révisé pour la compatibilité 64 bits.
Casting un pointeur vers un int est horrible du point de vue de la portabilité. La taille de int est définie par le mélange du compilateur et de l'architecture. C'est pourquoi l'en-tête stdint.h a été créé pour vous permettre d'indiquer explicitement la taille du type que vous utilisez sur de nombreuses plates-formes différentes avec de nombreuses tailles de mots différentes.
Vous feriez mieux de lancer un cast vers uintptr_t ou intptr_t (à partir de stdint.h, et choisissez celui qui correspond le mieux à la signature dont vous avez besoin).
Vous pouvez essayer d'utiliser intptr_t
pour une meilleure portabilité au lieu de int où des transtypages de pointeurs sont requis, tels que des rappels.
Vous ne voulez pas supprimer ces erreurs car, très probablement, elles indiquent un problème avec la logique du code.
Si vous supprimez les erreurs, cela pourrait même fonctionner pendant un certain temps. Alors que le pointeur pointe vers une adresse dans les 4 premiers Go, les 32 bits supérieurs seront 0 et vous ne perdrez aucune donnée. Mais une fois que vous obtenez une adresse> 4 Go, votre code commencera "mystérieusement" à ne pas fonctionner.
Ce que vous devez faire est de modifier tout int pouvant contenir un pointeur sur intptr_t.
Vous devez éditer manuellement les fichiers afin de les remplacer par du code qui n'est pas susceptible d'être bogué et non portable.
La suppression des avertissements est une mauvaise idée, mais il y a peut un indicateur de compilateur pour utiliser des entiers 64 bits, selon votre compilateur et votre architecture, et c'est un moyen sûr de résoudre le problème (en supposant bien sûr que le code ne supposait pas également que les entiers sont 32 bits). Pour gcc, le drapeau est -m64.
La meilleure réponse est toujours de corriger le code, je suppose, mais s'il s'agit d'un code tiers hérité et que ces avertissements sont généralisés, je ne peux pas voir ce refactoring comme une utilisation très efficace de votre temps. Cependant, ne jetez certainement pas de pointeurs sur des ints dans votre nouveau code.
Comme défini par la norme C++ actuelle, il n'y a pas de type entier garanti pour contenir un pointeur. Certaines plateformes auront un intptr_t, mais ce n'est pas une fonctionnalité standard de C++. Fondamentalement, le traitement des bits d'un pointeur comme s'ils étaient un entier n'est pas une chose portable à faire (bien qu'il puisse être fait pour fonctionner sur de nombreuses plates-formes).
Si la raison de la conversion est de rendre le pointeur opaque, alors void * y parvient déjà, donc le code pourrait utiliser void * au lieu de int. Un typedef pourrait rendre cela un peu plus agréable dans le code
typedef void * handle_t;
Si la raison de la conversion est de faire de l'arithmétique de pointeur avec une granularité d'octet, alors le meilleur moyen est probablement de convertir en un (char const *) et de faire le calcul avec cela.
Si la raison de la conversion est d'assurer la compatibilité avec une bibliothèque existante (peut-être une ancienne interface de rappel) qui ne peut pas être modifiée, je pense que vous devez consulter la documentation de cette bibliothèque. Si la bibliothèque est capable de prendre en charge les fonctionnalités dont vous avez besoin (même sur une plate-forme 64 bits), sa documentation peut répondre à la solution envisagée.
J'ai rencontré un problème similaire. Je l'ai résolu de la manière suivante:
#ifdef 64BIT
typedef uint64_t tulong;
#else
typedef uint32_t tulong;
#endif
void * ptr = NULL; //Whatever you want to keep it.
int i;
i = (int)(tulong)ptr;
Je pense que le problème est de transtyper un pointeur vers un type de données plus court. Mais pour un type plus grand à int
, cela fonctionne très bien.
J'ai converti ce problème de la conversion de type d'un pointeur en long
à la conversion de type d'un entier 64 bits en entier 32 bits et cela a bien fonctionné. Je suis toujours à la recherche d'une option de compilation dans GCC/Clang.
Parfois, il est judicieux de vouloir diviser un élément 64 bits en 2 éléments 32 bits. Voici comment vous le feriez:
En tête de fichier:
//You only need this if you haven't got a definition of UInt32 from somewhere else
typedef unsigned int UInt32;
//x, when cast, points to the lower 32 bits
#define LO_32(x) (*( (UInt32 *) &x))
//address like an array to get to the higher bits (which are in position 1)
#define HI_32(x) (*( ( (UInt32 *) &x) + 1))
Fichier source:
//Wherever your pointer points to
void *ptr = PTR_LOCATION
//32-bit UInt containing the upper bits
UInt32 upper_half = HI_32(ptr);
//32-bit UInt containing the lower bits
UInt32 lower_half = LO_32(ptr);