Est-ce un comportement non défini d'imprimer des pointeurs nuls avec le %p
spécificateur de conversion?
#include <stdio.h>
int main(void) {
void *p = NULL;
printf("%p", p);
return 0;
}
La question s’applique au standard C et non aux implémentations en C.
C'est l'un de ces cas étranges où nous sommes soumis aux limitations de la langue anglaise et à la structure incohérente de la norme. Donc, au mieux, je peux faire un contre-argument convaincant, car il est impossible de prouver :)1
Le code de la question présente un comportement bien défini.
Comme [7.1.4] est la base de la question, commençons par là:
Chacune des affirmations suivantes s'applique sauf indication explicite contraire dans les descriptions détaillées suivantes: Si un argument d'une fonction a une valeur non valide ( telle que , une valeur en dehors du domaine de la fonction, ou d'un pointeur en dehors de l'espace d'adressage du programme, ou d'un pointeur nul , [... autre exemples ...]) [...] le comportement est indéfini. [... autres déclarations ...]
C'est un langage maladroit. L'une des interprétations possibles est que les éléments de la liste sont UB pour toutes les fonctions de la bibliothèque, à moins d'être remplacés par les descriptions individuelles. Mais la liste commence par "tels que", ce qui indique qu’elle est illustrative et non exhaustive. Par exemple, il ne mentionne pas la terminaison null correcte des chaînes (critique pour le comportement de, par exemple, strcpy
).
Il est donc clair que l'intention/portée de 7.1.4 est simplement qu'une "valeur invalide" conduit à UB ( sauf indication contraire). Nous devons examiner la description de chaque fonction pour déterminer ce qui constitue une "valeur non valide".
strcpy
[7.21.2.3] ne dit que ceci:
La fonction
strcpy
copie la chaîne pointée pars2
(incluant le caractère nul final) dans le tableau pointé pars1
. Si la copie a lieu entre des objets qui se chevauchent, le comportement n'est pas défini.
Il ne fait aucune mention explicite des pointeurs nuls, mais ne mentionne pas non plus les terminateurs nuls. Au lieu de cela, on en déduit "chaîne désignée par s2
"que les seules valeurs valides sont des chaînes (c.-à-d. des pointeurs vers des tableaux de caractères à terminaison null).
En effet, ce schéma peut être vu à travers les descriptions individuelles. Quelques autres exemples:
[7.6.4.1 (fenv)] stocke l'environnement à virgule flottante actuel dans l'objet pointé vers par
envp
[7.12.6.4 (frexp)] stocke le nombre entier dans l'objet int pointé vers par
exp
[7.19.5.1 (fclose)] le flux pointé vers par
stream
printf
[7.19.6.1] dit ceci à propos de %p
:
p
- L'argument doit être un pointeur survoid
. 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.
Null est une valeur de pointeur valide et cette section ne mentionne pas explicitement que null est un cas spécial, ni que le pointeur doit pointer sur un objet. C'est donc un comportement défini.
1. Sauf si un auteur de normes se présente, ou si nous pouvons trouver quelque chose de similaire à un document justification qui clarifie les choses.
Oui. L'impression de pointeurs nuls avec le spécificateur de conversion %p
A un comportement indéfini. Cela dit, je ne suis au courant d'aucune implémentation de conformité existante qui se comporterait mal.
La réponse s'applique à n'importe laquelle des normes C (C89/C99/C11).
Le spécificateur de conversion %p
Attend un argument de type pointeur à annuler, la conversion du pointeur en caractères imprimables est définie par l'implémentation. Il n'indique pas qu'un pointeur nul est attendu.
L'introduction aux fonctions de bibliothèque standard indique que les pointeurs nuls comme arguments des fonctions (bibliothèque standard) sont considérés comme des valeurs non valides, sauf indication contraire explicite.
C99
/C11
§7.1.4 p1
[...] Si un argument d'une fonction a une valeur non valide (comme un [...] pointeur nul, le [...] comportement est indéfini.
Exemples de fonctions (bibliothèque standard) qui attendent des pointeurs nuls comme arguments valides:
fflush()
utilise un pointeur nul pour vider "tous les flux" (le cas échéant).freopen()
utilise un pointeur null pour indiquer le fichier "actuellement associé" au flux.snprintf()
permet de passer un pointeur nul lorsque 'n' est zéro.realloc()
utilise un pointeur null pour allouer un nouvel objet.free()
permet de passer un pointeur nul.strtok()
utilise un pointeur nul pour les appels suivants.Si nous prenons le cas de snprintf()
, il est logique d'autoriser le passage d'un pointeur nul lorsque 'n' est égal à zéro, mais ce n'est pas le cas des autres fonctions (bibliothèque standard) qui autorisent un zéro similaire 'n '. Par exemple: memcpy()
, memmove()
, strncpy()
, memset()
, memcmp()
.
Il est non seulement spécifié dans l'introduction à la bibliothèque standard, mais également dans l'introduction à ces fonctions:
C99 §7.21.1 p2
/C11 §7.24.1 p2
Lorsqu'un argument déclaré comme
size_t
N spécifie la longueur du tableau pour une fonction, n peut avoir la valeur zéro lors d'un appel de cette fonction. Sauf indication contraire explicite dans la description d'une fonction particulière dans le présent sous-paragraphe, les arguments de pointeur sur un tel appel doivent toujours avoir des valeurs valides telles que décrites au 7.1.4.
Je ne sais pas si le UB de %p
Avec un pointeur nul est en fait intentionnel, mais puisque la norme indique explicitement que les pointeurs nuls sont considérés comme des valeurs non valides en tant qu'arguments des fonctions de bibliothèque standard, spécifie les cas où un pointeur NULL est un argument valide (snprintf, free, etc.), puis répète et répète l'exigence selon laquelle les arguments doivent être valides même dans des cas nuls (n ')' (memcpy
, memmove
, memset
), alors je pense qu'il est raisonnable de supposer que le comité de normalisation C n'est pas trop préoccupé par le fait de ne pas définir de telles choses.