Problèmes «Pointeur à partir d’un entier / entier d’un pointeur sans transt»

Cette question est censée être une entrée de FAQ pour toutes les questions d’initialisation / assignation entre un entier et un pointeur


Je veux faire écrire du code où un pointeur est défini sur une adresse mémoire spécifique, par exemple 0x12345678 . Mais lors de la compilation de ce code avec le compilateur gcc, j’obtiens “warnings / errors d’initialisation: rend le pointeur à partir d’un entier sans cast”:

 int* p = 0x12345678; 

De même, ce code donne “l’initialisation crée un entier à partir d’un pointeur sans transtypage”:

 int* p = ...; int i = p; 

Si je fais la même chose en dehors de la ligne de déclaration de variable, le message est le même mais dit “affectation” au lieu de “initialisation”:

 p = 0x12345678; // "assignment makes pointer from integer without a cast" i = p; // "assignment makes integer from pointer without a cast" 

Les tests avec d’autres compilateurs populaires donnent également des messages d’erreur / avertissement:

  • Clang dit “conversion d’un entier incompatible en pointeur”
  • icc dit “une valeur de type int ne peut pas être utilisée pour initialiser une entité de type int*
  • MSVC (cl) indique que “l’initialisation de int* diffère des niveaux d’indirection par rapport à int “.

Question: Les exemples ci-dessus sont-ils valables C?


Et une question de suivi:

Cela ne donne aucun avertissement / erreur:

 int* p = 0; 

Pourquoi pas?

    Non, il n’est pas valide C et n’a jamais été valide C. Ces exemples constituent une violation de contrainte de la norme.

    La norme ne vous permet pas d’initialiser / d’affecter un pointeur sur un entier, ni un entier sur un pointeur. Vous devez forcer manuellement une conversion de type avec un transtypage:

     int* p = (int*) 0x1234; int i = (int)p; 

    Si vous n’utilisez pas la conversion, le code n’est pas valide C et votre compilateur n’est pas autorisé à laisser le code passer sans afficher de message. Plus précisément, cela est régi par les règles d’ assignation simple , C17 6.5.16.1 §1:

    6.5.16.1 Affectation simple

    Contraintes

    L’un des suivants doit contenir:

    • l’opérande gauche a un type arithmétique atomique, qualifié ou non qualifié, et le droit a un type arithmétique;
    • l’opérande de gauche a une version atomique, qualifiée ou non qualifiée d’une structure ou d’un type d’union compatible avec le type de la droite;
    • l’opérande gauche a un type de pointeur atomique, qualifié ou non qualifié, et (compte tenu du type que l’opérande gauche aurait après la conversion de lvalue), les deux opérandes sont des pointeurs vers des versions qualifiées ou non qualifiées de types compatibles, et le type pointé vers la gauche a les qualificatifs du type indiqué par la droite;
    • l’opérande gauche a un type de pointeur atomique, qualifié ou non qualifié, et (compte tenu du type que l’opérande gauche aurait après la conversion de lvalue), un opérande est un pointeur sur un type d’object, et l’autre est un pointeur sur une version qualifiée ou non qualifiée de void, et le type pointé par la gauche a tous les qualificatifs du type pointé par la droite;
    • l’opérande de gauche est un pointeur atomique, qualifié ou non qualifié, et la droite est une constante de pointeur nulle; ou
    • l’opérande de gauche a le type atomique, qualifié ou non qualifié, _Bool, et le droit est un pointeur.

    En cas de int* p = 0x12345678; , l’opérande gauche est un pointeur et la droite est un type arithmétique.
    En cas d’ int i = p; , l’opérande gauche est un type arithmétique et la droite est un pointeur.
    Aucune de celles-ci ne correspond aux contraintes mentionnées ci-dessus.

    Quant à pourquoi int* p = 0; fonctionne, c’est un cas particulier. L’opérande de gauche est un pointeur et la droite est une constante de pointeur nulle . Plus d’informations sur la différence entre les pointeurs null, les constantes de pointeur null et la macro NULL .


    Quelques points à noter:

    • Si vous affectez une adresse brute à un pointeur, celui-ci devra probablement être qualifié de manière volatile , étant donné qu’il pointe vers quelque chose comme un registre matériel ou un emplacement de mémoire EEPROM / Flash, qui peut en modifier le contenu au moment de l’exécution.

    • La conversion d’un pointeur en entier n’est en aucun cas garantie de fonctionner même avec le casting. La norme (C17 6.3.2.3 §5 et §6 dit):

      Un entier peut être converti en n’importe quel type de pointeur. Sauf indication contraire, le résultat est défini par l’implémentation, peut ne pas être correctement aligné, peut ne pas pointer vers une entité du type référencé et peut être une représentation d’interruption. 68)

      Tout type de pointeur peut être converti en un type entier. Sauf indication contraire, le résultat est défini par l’implémentation. Si le résultat ne peut pas être représenté dans le type entier, le comportement est indéfini. Le résultat ne doit pas forcément être compris dans la plage de valeurs d’un type entier.

      Note de bas de page informative:

      68) Les fonctions de mappage permettant de convertir un pointeur en entier ou un entier en pointeur sont conçues pour être cohérentes avec la structure d’adressage de l’environnement d’exécution.

      De plus, l’adresse d’un pointeur peut être plus grande que celle contenue dans un int , comme c’est le cas pour la plupart des systèmes 64 bits. Par conséquent, il est préférable d’utiliser le uintptr_t de