Corriger le spécificateur de format pour imprimer le pointeur ou l'adresse?

194

Quel spécificateur de format dois-je utiliser pour imprimer l'adresse d'une variable? Je suis confus entre le lot ci-dessous.

% u - entier non signé

% x - valeur hexadécimale

% p - pointeur vide

Quel serait le format optimal pour imprimer une adresse?

San
la source

Réponses:

240

La réponse la plus simple, en supposant que vous ne vous souciez pas des aléas et des variations de format entre les différentes plates-formes, est la %pnotation standard .

La norme C99 (ISO / CEI 9899: 1999) dit au §7.19.6.1 ¶8:

pL'argument doit être un pointeur vers void. La valeur du pointeur est convertie en une séquence de caractères d'impression, d'une manière définie par l'implémentation.

(Dans C11 - ISO / CEI 9899: 2011 - les informations se trouvent au §7.21.6.1 ¶8.)

Sur certaines plates-formes, cela inclura un début 0xet sur d'autres non, et les lettres pourraient être en minuscules ou en majuscules, et le standard C ne définit même pas qu'il s'agira d'une sortie hexadécimale bien que je sache aucune implémentation là où ce n'est pas le cas.

Il est quelque peu ouvert à débattre de la nécessité de convertir explicitement les pointeurs avec une (void *)distribution. C'est être explicite, ce qui est généralement bien (c'est donc ce que je fais), et la norme dit que «l'argument sera un pointeur vers void». Sur la plupart des machines, vous vous en sortiriez en omettant une distribution explicite. Cependant, cela serait important sur une machine où la représentation binaire d'une char *adresse pour un emplacement mémoire donné est différente de l' adresse « pointeur autre chose » pour le même emplacement mémoire. Ce serait une machine à adressage par mot, au lieu d'adressage par octet. De telles machines ne sont pas courantes (probablement pas disponibles) ces jours-ci, mais la première machine sur laquelle j'ai travaillé après l'université en était une (ICL Perq).

Si vous n'êtes pas satisfait du comportement défini par l'implémentation de %p, utilisez C99 <inttypes.h>et à la uintptr_tplace:

printf("0x%" PRIXPTR "\n", (uintptr_t)your_pointer);

Cela vous permet d'affiner la représentation en fonction de vous-même. J'ai choisi d'avoir les chiffres hexadécimaux en majuscules pour que le nombre soit uniformément de la même hauteur et que le creux caractéristique au début de 0xA1B2CDEFapparaisse ainsi, pas comme celui 0xa1b2cdefqui descend le long du nombre aussi. Votre choix cependant, dans des limites très larges. Le (uintptr_t)cast est sans ambiguïté recommandé par GCC lorsqu'il peut lire la chaîne de format au moment de la compilation. Je pense qu'il est correct de demander le casting, même si je suis sûr que certains ignoreraient l'avertissement et s'en tireraient la plupart du temps.


Kerrek demande dans les commentaires:

Je suis un peu confus au sujet des promotions standard et des arguments variadiques. Est-ce que tous les pointeurs sont promus comme annulés *? Sinon, s'il y int*avait, disons, deux octets et void*4 octets, alors ce serait clairement une erreur de lire quatre octets de l'argument, non?

J'étais dans l'illusion que la norme C dit que tous les pointeurs d'objet doivent être de la même taille, donc void *et int *ne peuvent pas être de tailles différentes. Cependant, ce que je pense être la section pertinente de la norme C99 n'est pas si catégorique (bien que je ne connaisse pas une implémentation où ce que j'ai suggéré est vrai est en fait faux):

§6.2.5 Types

¶26 Un pointeur vers void doit avoir les mêmes exigences de représentation et d'alignement qu'un pointeur vers un type de caractère. 39) De même, les pointeurs vers des versions qualifiées ou non de types compatibles doivent avoir les mêmes exigences de représentation et d'alignement. Tous les pointeurs vers des types de structure doivent avoir les mêmes exigences de représentation et d'alignement les uns que les autres. Tous les pointeurs vers des types d'union doivent avoir les mêmes exigences de représentation et d'alignement les uns que les autres. Les pointeurs vers d'autres types n'ont pas besoin d'avoir les mêmes exigences de représentation ou d'alignement.

39) Les mêmes exigences de représentation et d'alignement sont censées impliquer l'interchangeabilité en tant qu'arguments de fonctions, valeurs de retour de fonctions et membres d'unions.

(C11 dit exactement la même chose dans la section §6.2.5, ¶28 et note de bas de page 48.)

Ainsi, tous les pointeurs vers des structures doivent être de la même taille les uns que les autres et doivent partager les mêmes exigences d'alignement, même si les structures vers lesquelles pointent les pointeurs peuvent avoir des exigences d'alignement différentes. De même pour les syndicats. Les pointeurs de caractères et les pointeurs vides doivent avoir les mêmes exigences de taille et d'alignement. Les pointeurs vers des variations de int(signification unsigned intet signed int) doivent avoir les mêmes exigences de taille et d'alignement les uns que les autres; de même pour les autres types. Mais la norme C ne le dit pas formellement sizeof(int *) == sizeof(void *). Eh bien, SO est bon pour vous faire inspecter vos hypothèses.

Le standard C n'exige définitivement pas que les pointeurs de fonction aient la même taille que les pointeurs d'objet. Cela était nécessaire pour ne pas casser les différents modèles de mémoire sur les systèmes de type DOS. Là, vous pourriez avoir des pointeurs de données 16 bits mais des pointeurs de fonction 32 bits, ou vice versa. C'est pourquoi la norme C n'impose pas que les pointeurs de fonction puissent être convertis en pointeurs d'objet et vice versa.

Heureusement (pour les programmeurs ciblant POSIX), POSIX intervient dans la brèche et exige que les pointeurs de fonction et les pointeurs de données soient de la même taille:

§2.12.3 Types de pointeurs

Tous les types de pointeur de fonction doivent avoir la même représentation que le pointeur de type vers void. La conversion d'un pointeur de fonction en void *ne doit pas modifier la représentation. Une void *valeur résultant d'une telle conversion peut être reconvertie dans le type de pointeur de fonction d'origine, à l'aide d'un cast explicite, sans perte d'informations.

Remarque: la norme ISO C ne l'exige pas, mais elle est requise pour la conformité POSIX.

Ainsi, il semble que les transtypages explicites en void *sont fortement recommandés pour une fiabilité maximale dans le code lors du passage d'un pointeur vers une fonction variadique telle que printf(). Sur les systèmes POSIX, il est sûr de convertir un pointeur de fonction en pointeur vide pour l'impression. Sur d'autres systèmes, ce n'est pas nécessairement sûr de faire cela, ni nécessairement de passer des pointeurs autrement que void *sans lancer.

Jonathan Leffler
la source
3
Je suis un peu confus au sujet des promotions standard et des arguments variadiques. Tous les pointeurs font-ils l'objet d'une promotion standard void*? Sinon, s'il y int*avait, disons, deux octets et void*4 octets, alors ce serait clairement une erreur de lire quatre octets de l'argument, non?
Kerrek SB le
Notez qu'une mise à jour de POSIX (POSIX 2013) a supprimé la section 2.12.3, déplaçant la plupart des exigences vers la dlsym()fonction à la place. Un jour, j'écrirai le changement ... mais «un jour» n'est pas «aujourd'hui».
Jonathan Leffler
Cette réponse s'applique-t-elle également aux pointeurs vers des fonctions? Peuvent-ils être convertis en void *? Hmm, je vois votre commentaire ici . Puisque seule une conversion d'un wat est nécessaire (pointeur de fonction vers void *), cela fonctionne alors?
chux
@chux: Strictement, la réponse est «non», mais en pratique, la réponse est «oui». La norme C ne garantit pas que les pointeurs de fonction peuvent être convertis en a void *et retour sans perte d'informations. Pragmatiquement, il existe très peu de machines où la taille d'un pointeur de fonction n'est pas la même que la taille d'un pointeur d'objet. Je ne pense pas que la norme fournisse une méthode d'impression d'un pointeur de fonction sur des machines où la conversion est problématique.
Jonathan Leffler
"et retour sans perte d'informations" n'est pas pertinent pour l'impression. Est ce que ça aide?
chux - Réintégrer Monica
50

pest le spécificateur de conversion pour imprimer des pointeurs. Utilisez ceci.

int a = 42;

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

N'oubliez pas que l'omission de la pconversion est un comportement indéfini et que l'impression avec le spécificateur de conversion est effectuée d'une manière définie par l'implémentation.

ouah
la source
2
Pardon, pourquoi omettre le casting est un "comportement indéfini"? Cela importe-t-il de quelle variable il s'agit, si tout ce dont vous avez besoin est l'adresse, pas la valeur?
valdo
9
@valdo parce que C le dit (C99, 7.19.6.1p8) "p L'argument doit être un pointeur vers void."
ouah
12
@valdo: Ce n'est pas nécessairement le cas que tous les pointeurs aient la même taille / représentation.
caf
32

Utilisez %p, pour "pointer", et n'utilisez rien d'autre *. Vous n'êtes pas garanti par la norme que vous êtes autorisé à traiter un pointeur comme n'importe quel type d'entier particulier, donc vous obtiendriez en fait un comportement indéfini avec les formats intégraux. (Par exemple, %uattend un unsigned int, mais que se passe-t-il si void*a une taille ou un alignement différent de celui unsigned int?)

*) [Voir la bonne réponse de Jonathan!] Alternativement %p, vous pouvez utiliser des macros spécifiques au pointeur de <inttypes.h>, ajoutées dans C99.

Tous les pointeurs d'objet sont implicitement convertibles void*en C, mais pour passer le pointeur en tant qu'argument variadique, vous devez le convertir explicitement (car les pointeurs d'objet arbitraires ne sont que convertibles , mais pas identiques aux pointeurs void):

printf("x lives at %p.\n", (void*)&x);
Kerrek SB
la source
2
Tous les pointeurs d' objet sont convertibles en void *(bien que printf()vous ayez techniquement besoin de la conversion explicite, car c'est une fonction variadique). Les pointeurs de fonction ne sont pas nécessairement convertibles en void *.
caf
@caf: Oh, je ne connaissais pas les arguments variadiques - corrigé! Merci!
Kerrek SB
2
La norme C n'exige pas que les pointeurs de fonction soient convertibles void *et retournés en pointeur de fonction sans perte; heureusement, cependant, POSIX l'exige explicitement (en notant qu'il ne fait pas partie du standard C). Donc, en pratique, vous pouvez vous en tirer (conversion void (*function)(void)vers void *et retour vers void (*function)(void)), mais strictement cela n'est pas exigé par le standard C.
Jonathan Leffler
2
Jonathan et R.: Tout cela est très intéressant, mais je suis presque sûr que nous n'essayons pas d'imprimer des pointeurs de fonction ici, alors peut-être que ce n'est pas tout à fait le bon endroit pour en discuter. Je préfère de loin voir un soutien ici pour mon insistance à ne pas utiliser %u!
Kerrek SB
2
%uet %lusont faux sur toutes les machines , pas certaines machines. La spécification de printfest très claire que lorsque le type passé ne correspond pas au type requis par le spécificateur de format, le comportement n'est pas défini. Que la taille des types corresponde (qui peut être vraie ou fausse, selon la machine) n'est pas pertinente; ce sont les types qui doivent correspondre, et ils ne le seront jamais.
R .. GitHub STOP HELPING ICE
9

Au lieu des autres (très bonnes) réponses, vous pouvez effectuer un cast vers uintptr_tou intptr_t(à partir de stdint.h/ inttypes.h) et utiliser les spécificateurs de conversion d'entiers correspondants. Cela permettrait une plus grande flexibilité dans la façon dont le pointeur est formaté, mais à proprement parler, une implémentation n'est pas nécessaire pour fournir ces typedefs.

R .. GitHub STOP AIDING ICE
la source
considérer #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } est-il un comportement indéfini pour imprimer l'adresse de la variable à l'aide du %uspécificateur de format? L'adresse de la variable dans la plupart des cas est positive, puis-je utiliser à la %uplace de %p?
Destructor
1
@Destructor: Non, %uest un format de unsigned inttype et ne peut pas être utilisé avec un argument pointeur vers printf.
R .. GitHub STOP HELPING ICE
-1

Vous pouvez utiliser %xou %Xou %p; tous sont corrects.

  • Si vous utilisez %x, l'adresse est donnée en minuscules, par exemple:a3bfbc4
  • Si vous utilisez %X, l'adresse est indiquée en majuscules, par exemple:A3BFBC4

Les deux sont corrects.

Si vous utilisez %xou si vous %Xenvisagez six positions pour l'adresse, et si vous utilisez, %pcela considère huit positions pour l'adresse. Par exemple:

Pacha
la source
1
Bienvenue à SO. Veuillez prendre le temps de revoir les autres réponses, elles expliquent clairement un certain nombre de détails que vous oubliez.
AntoineL