web-dev-qa-db-fra.com

Pourquoi est-ce que j'obtiens l'erreur "cast du pointeur vers l'entier de taille différente"?

La ligne suivante (pure c) compile proprement sur windows (win7 64 bits + codeblocks 13 + mingw32) et debian (wheezy 32 bits + codeblocks 10 + gcc) mais déclenche un avertissement sur kali (64 bits + codes blocs + gcc). Des commentaires? Pourquoi ai-je cet avertissement alors que la même ligne est compilée sans aucun avertissement sous Windows et Debian?

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    unsigned int offset = (unsigned int) dst % blkLen; // warning here!
    // some code cont...
}

Le message dans codeblocks est le suivant: " erreur: conversion du pointeur en entier de taille différente [-Werror = pointeur-à-int-cast]"

remarque: les options du compilateur sont -std=c99 -Werror -save-temps (identiques sur les trois systèmes).

edit 2: Bien que j’ai réussi à le compiler sans avertir à l’aide des lignes de préprocesseur ci-dessous, @ Keith Thompson (voir ci-dessous) a un point crucial à ce sujet. Donc, ma dernière décision utilise uintptr_t serait un meilleur choix.

modifier 1: Merci pour tout le monde a répondu. Comme le notent toutes les réponses, le problème est un problème 32 bits contre 64 bits. J'ai inséré les lignes de préprocesseur suivantes:

#if __linux__   //  or #if __GNUC__
    #if __x86_64__ || __ppc64__
        #define ENVIRONMENT64
    #else
        #define ENVIRONMENT32
    #endif
#else
    #if _WIN32
        #define ENVIRONMENT32
    #else
        #define ENVIRONMENT64
    #endif
#endif // __linux__

#ifdef ENVIRONMENT64
    #define MAX_BLOCK_SIZE unsigned long long int
#else
    #define MAX_BLOCK_SIZE unsigned long int
#endif // ENVIRONMENT64

puis a remplacé la ligne de problème en tant que:

unsigned int offset = (MAX_BLOCK_SIZE) dst % blkLen;

Maintenant, tout semble bien se passer.

12
ssd

La raison de cet avertissement est que le compilateur pense que vous pourriez essayer d’aller-retour d’un pointeur à travers int et vice-versa. C'était une pratique courante avant l'avènement des machines 64 bits et ce n'est ni sûr ni raisonnable. Bien sûr, ici, le compilateur peut clairement voir que vous ne le faites pas, et ce serait bien si cela était assez intelligent pour éviter l’avertissement dans de tels cas, mais ce n’est pas le cas.

Une alternative propre qui évite l'avertissement, et un autre problème beaucoup plus grave de résultat erroné lorsque la valeur convertie est négative, est la suivante:

unsigned int offset = (uintptr_t) dst % blkLen;

Vous devez inclure stdint.h ou inttypes.h pour pouvoir disposer de uintptr_t.

19
R..

Le problème est que la conversion d'un pointeur void* en unsigned int est par nature non portable.

La différence de taille possible n’est qu’une partie du problème. Cette partie du problème peut être résolue en utilisant uintptr_t, un type défini dans <stdint.h> et <inttypes.h>. Il est garanti que uintptr_t est suffisamment large pour que la conversion d'un void* en uintptr_t et inversement produise la valeur du pointeur d'origine (ou au moins une valeur de pointeur identique à celle d'origine). Il y a aussi un type intptr_t, qui est signé; les types généralement non signés ont plus de sens pour ce genre de chose. Il n'est pas garanti que uintptr_t et intptr_t existent, mais ils devraient exister sur toute implémentation (C99 ou ultérieure) comportant les types entiers appropriés.

Toutefois, même si vous avez un type entier suffisamment grand pour contenir un pointeur converti, le résultat n'a pas nécessairement de sens, sauf pour la conversion en pointeur.

La norme C dit, dans une note de bas de page non normative, que:

Les fonctions de mappage permettant de convertir un pointeur en entier ou en entier à un pointeur sont destinés à être cohérents avec l'adressage structure de l'environnement d'exécution.

ce qui n’est utile que si vous connaissez la structure d’adressage.

Vous semblez essayer de déterminer le décalage de l'argument void* par rapport au prochain multiple inférieur de blkLen; en d'autres termes, vous essayez de déterminer comment la valeur du pointeur est alignée par rapport à blkLen blocs de mémoire de taille.

Si vous savez que c’est une chose sensée à faire sur le système que vous utilisez , c’est bien. Mais vous devez savoir que les opérations arithmétiques sur les entiers résultant de conversions de pointeurs sont toujours non portables par nature.

Un exemple concret: j'ai travaillé sur des systèmes (machines vectorielles Cray) où un pointeur void* est une adresse machine 64 bits (qui pointe vers un mot 64 bits), avec un décalage d'octet de 3 bits inséré par logiciel dans le cas contraire inutilisé ordre élevé 3 bits. La conversion d'un pointeur en entier a simplement copié la représentation. Toute arithmétique entière sur un tel entier est susceptible de donner des résultats dénués de sens sauf si elle prend en compte cette représentation (certes exotique).

Conclusions:

  1. Vous devez absolument utiliser uintptr_t plutôt que de jouer aux astuces du préprocesseur pour déterminer le type d’entier que vous pouvez utiliser. L'implémenteur de votre compilateur a déjà déterminé le type d'entier pouvant contenir en toute sécurité une valeur de pointeur convertie. Il n'est pas nécessaire de réinventer cette roue en particulier. (Avertissement: <stdint.h> a été ajouté à C par la norme ISO de 1999. Si vous êtes coincé avec un compilateur ancien qui ne l'implémente pas, vous devrez peut-être utiliser une sorte de piratage #ifdef. Mais je suggérerais quand même d'utiliser uintptr_t. Si elle est disponible, vous pouvez tester __STDC_VERSION__ >= 199901L pour vérifier la conformité à C99 - bien que certains compilateurs puissent prendre en charge <stdint.h> sans prendre en charge complètement C99.)

  2. Vous devez savoir que convertir un pointeur en entier et jouer avec sa valeur n'est pas portable. Cela ne veut pas dire que vous ne devriez pas le faire; L'un des plus grands atouts de C est sa capacité à prendre en charge non-portable le code quand vous en avez besoin.

10
Keith Thompson

Parce que transtyper un void * en unsigned int correspond exactement à ce que cet avertissement est destiné à détecter, car il est dangereux. Le pointeur peut être en 64 bits et la variable int en 32 bits. Pour toute plate-forme donnée, il n'est pas garanti que sizeof(unsigned int) soit sizeof(void *). Vous devriez utiliser uintptr_t à la place.

3
b4hand

Peut-être parce que sur une architecture 64 bits, un pointeur est long de 64 bits et un int de 32 bits seulement?

Tu devrais essayer 

void* foo(void *dst, ...) {
    // some code
    unsigned int blkLen = sizeof(int); // this line ok.
    uintptr_t offset = (uintptr_t) dst % blkLen; // warning here!
    // some code cont...
}
2
Serge Ballesta

Je pense que vous obtenez cet avertissement car la taille de int dépend des implémentations, par exemple int peut être long de 2 octets ou long de 4 octets. Cela pourrait être la raison de l'avertissement (corrigez-moi si je me trompe). Mais de toute façon, pourquoi essayez-vous de faire un modulo sur un pointeur.

1
viggy22

Vous avez fait la macro mais ne pensez-vous pas que c'est toujours faux. Parce que votre pointeur sera converti en unsigned long long int ou unsigned long int, qui seront 32 bits et 64 bits dans les systèmes d’exploitation 86x et 64x, mais que votre variable sera offset unsigned int, qui est en 32 bits pour les systèmes 64x et 86x .. vous devez également convertir le décalage en macro.

Ou simplement, vous pouvez également convertir le pointeur en long (c'est-à-dire unsigned int en long) et décaler en long (c'est-à-dire unsigned int en long).

0
newbie