J'essaie d'adapter un code existant à une machine 64 bits. Le problème principal est que, dans une fonction, le codeur précédent utilise un argument void * converti en un type approprié dans la fonction elle-même. Un petit exemple:
void function(MESSAGE_ID id, void* param)
{
if(id == FOO) {
int real_param = (int)param;
// ...
}
}
Bien sûr, sur une machine 64 bits, j'ai l'erreur:
error: cast from 'void*' to 'int' loses precision
Je voudrais corriger cela pour qu'il fonctionne toujours sur une machine 32 bits et aussi proprement que possible. Une idée ?
Utilisez intptr_t
et uintptr_t
.
Pour vous assurer qu'il est défini de manière portable, vous pouvez utiliser le code suivant:
#if defined(__BORLANDC__)
typedef unsigned char uint8_t;
typedef __int64 int64_t;
typedef unsigned long uintptr_t;
#Elif defined(_MSC_VER)
typedef unsigned char uint8_t;
typedef __int64 int64_t;
#else
#include <stdint.h>
#endif
Il suffit de placer cela dans un fichier .h et d'inclure où vous en avez besoin.
Vous pouvez également télécharger la version Microsoft du fichier stdint.h
à partir de ici ou utiliser un fichier portable à partir de ici .
Je dirais que c'est la manière moderne de C++.
#include <cstdint>
void *p;
auto i = reinterpret_cast<std::uintptr_t>(p);
MODIFIER:
la bonne façon de stocker un pointeur en tant qu'entier consiste donc à utiliser les types uintptr_t
ou intptr_t
. (Voir aussi cppreference types entiers pour C99 ).
ces types sont définis dans <stdint.h>
pour C99 et dans l'espace de noms std
pour C++ 11 dans <cstdint>
(voir types entiers pour C++ ).
C++ 11 (et versions ultérieures) Version
#include <cstdint>
std::uintptr_t i;
C++ 03 Version
extern "C" {
#include <stdint.h>
}
uintptr_t i;
Version C99
#include <stdint.h>
uintptr_t i;
En C, il n'y a qu'une seule conversion et l'utilisation de la version C en C++ est désapprouvée (donc, ne l'utilisez pas en C++). En C++, il y a différents moulages. reinterpret_cast
est la conversion correcte pour cette conversion (voir aussi ici ).
C++ 11 Version
auto i = reinterpret_cast<std::uintptr_t>(p);
C++ 03 Version
uintptr_t i = reinterpret_cast<uintptr_t>(p);
C Version
uintptr_t i = (uintptr_t)p; // C Version
'size_t' et 'ptrdiff_t' sont nécessaires pour correspondre à votre architecture (quelle qu'elle soit). Par conséquent, je pense que plutôt que d'utiliser "int", vous devriez pouvoir utiliser "size_t", qui sur un système 64 bits devrait être un type 64 bits.
Cette discussion unsigned int vs size_t va un peu plus en détail.
Utilisez uintptr_t
comme type entier.
Plusieurs réponses ont indiqué que uintptr_t
et #include <stdint.h>
étaient «la» solution. C’est, à mon avis, une partie de la réponse, mais pas la réponse dans son ensemble. Vous devez également rechercher l'endroit où la fonction est appelée avec l'ID de message de FOO.
Considérez ce code et cette compilation:
$ cat kk.c
#include <stdio.h>
static void function(int n, void *p)
{
unsigned long z = *(unsigned long *)p;
printf("%d - %lu\n", n, z);
}
int main(void)
{
function(1, 2);
return(0);
}
$ rmk kk
gcc -m64 -g -O -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith \
-Wcast-qual -Wstrict-prototypes -Wmissing-prototypes \
-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE kk.c -o kk
kk.c: In function 'main':
kk.c:10: warning: passing argument 2 of 'func' makes pointer from integer without a cast
$
Vous remarquerez qu'il y a un problème à l'emplacement de l'appelant (dans main()
): la conversion d'un entier en pointeur sans conversion. Vous allez devoir analyser votre function()
dans toutes ses utilisations pour voir comment les valeurs lui sont transmises. Le code dans ma function()
fonctionnerait si les appels étaient écrits:
unsigned long i = 0x2341;
function(1, &i);
Etant donné que les vôtres sont probablement écrits différemment, vous devez vérifier les points où la fonction est appelée pour vous assurer qu'il est judicieux d'utiliser la valeur comme indiqué. N'oubliez pas que vous êtes peut-être en train de trouver un bug latent.
De même, si vous souhaitez formater la valeur du paramètre void *
(converti), examinez attentivement l'en-tête <inttypes.h>
(au lieu de stdint.h
- inttypes.h
fournit les services de stdint.h
, ce qui est inhabituel, mais la norme C99 dit [t ] L'en-tête <inttypes.h>
inclut l'en-tête <stdint.h>
et l'étend avec des fonctionnalités supplémentaires fournies par les implémentations hébergées ) et utilise les macros PRIxxx dans vos chaînes de format.
De plus, mes commentaires sont strictement applicables au C plutôt qu'au C++, mais votre code est dans le sous-ensemble du C++ portable entre le C et le C++. Les chances sont bonnes à bonnes que mes commentaires s’appliquent.
#include <stdint.h>
uintptr_t
défini dans le fichier d'en-tête standard inclus.Je pense que la "signification" de void * dans ce cas est un descripteur générique . Ce n'est pas un pointeur sur une valeur, mais bien la valeur elle-même . Programmeurs C et C++.)
Si elle contient une valeur entière, il vaut mieux qu'elle soit dans la plage des nombres entiers!
Voici un rendu facile en entier:
int x = (char*)p - (char*)0;
Il devrait seulement donner un avertissement.
La meilleure chose à faire est d'éviter de convertir le type de pointeur en type de non-pointeur ..__ Cependant, ce n'est clairement pas possible dans votre cas.
Comme tout le monde l’a dit, c’est le uintptr_t que vous devriez utiliser.
This link contient de bonnes informations sur la conversion en code 64 bits.
Il y a aussi une bonne discussion à ce sujet sur comp.std.c
Je suis tombé sur cette question en étudiant le code source de SQLite .
Dans le sqliteInt.h , il y a un paragraphe de code définissant une macro convertie entre un entier et un pointeur. L’auteur a fait une très bonne déclaration, soulignant d’abord que c’était un problème dépendant du compilateur, puis avait implémenté la solution pour prendre en compte la plupart des compilateurs populaires.
#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */
# define SQLITE_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X))
# define SQLITE_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X))
#Elif !defined(__GNUC__) /* Works for compilers other than LLVM */
# define SQLITE_INT_TO_PTR(X) ((void*)&((char*)0)[X])
# define SQLITE_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0))
#Elif defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */
# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X))
# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X))
#else /* Generates a warning - but it always works */
# define SQLITE_INT_TO_PTR(X) ((void*)(X))
# define SQLITE_PTR_TO_INT(X) ((int)(X))
#endif
Et voici une citation du commentaire pour plus de détails:
/*
** The following macros are used to cast pointers to integers and
** integers to pointers. The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.
**
** The correct "ANSI" way to do this is to use the intptr_t type.
** Unfortunately, that typedef is not available on all compilers, or
** if it is available, it requires an #include of specific headers
** that vary from one machine to the next.
**
** Ticket #3860: The llvm-gcc-4.2 compiler from Apple chokes on
** the ((void*)&((char*)0)[X]) construct. But MSVC chokes on ((void*)(X)).
** So we have to define the macros in different ways depending on the
** compiler.
*/
Le mérite en revient aux committers.
Puisque uintptr_t
est il n’est pas garanti qu’il existe en C++/C++ 11 , s’il s’agit d’une conversion à sens unique, vous pouvez envisager uintmax_t
, toujours défini dans <cstdint>
.
auto real_param = reinterpret_cast<uintmax_t>(param);
Pour jouer en toute sécurité, on peut ajouter n'importe où dans le code une assertion:
static_assert(sizeof (uintmax_t) >= sizeof (void *) ,
"No suitable integer type for conversion from pointer type");