93 votes

L'impression de pointeurs nuls avec %p est un comportement non défini ?

Est-il un comportement indéfini d'imprimer des pointeurs nuls avec la fonction %p spécificateur de conversion ?

#include <stdio.h>

int main(void) {
    void *p = NULL;

    printf("%p", p);

    return 0;
}

La question s'applique à la norme C, et non aux implémentations C.

0 votes

En fait, je ne pense pas que quiconque (y compris le comité C) s'en soucie trop. Il s'agit d'un problème tout à fait artificiel, sans signification pratique (ou presque).

0 votes

C'est parce que printf ne fait qu'afficher la valeur, et ne touche pas (dans le sens de la lecture ou de l'écriture de l'objet pointé) - ne peut pas être UB i le pointeur a une valeur valide pour son type (NULL est le valide valeur)

3 votes

@PeterJ disons que ce que vous dites est vrai (bien que la norme stipule clairement le contraire), le seul fait que nous débattons sur ce sujet rend la question valide et correcte, car il semble que la partie de la norme citée ci-dessous rende très difficile la compréhension de ce qui se passe pour un développeur ordinaire. Autrement dit, la question ne mérite pas le vote négatif, car ce problème nécessite une clarification !

93voto

Oli Charlesworth Points 148744

C'est l'un de ces cas particuliers où nous sommes soumis aux limites 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 le :) 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 déclarations suivantes s'applique, sauf mention contraire explicite dans les descriptions détaillées qui suivent : Si un argument d'une fonction a une valeur invalide ( comme une valeur hors du domaine de la fonction, ou un pointeur hors de l'espace d'adressage du programme, ou un pointeur nul , [... autres exemples ...] ) [...] le comportement est indéfini. [... autres déclarations ...]

C'est un langage maladroit. Une interprétation possible est que les éléments de la liste sont l'UB pour toutes les fonctions de la bibliothèque, à moins qu'ils ne soient remplacés par les descriptions individuelles. Mais la liste commence par "telles que", ce qui indique qu'elle est illustrative et non exhaustive. Par exemple, elle ne mentionne pas la terminaison nulle correcte des chaînes de caractères (critique pour le comportement de par ex. strcpy ).

Ainsi, il est clair que l'intention/la portée de 7.1.4 est simplement qu'une "valeur invalide" conduit à UB ( sauf indication contraire ). Il faut se reporter à la description de chaque fonction pour déterminer ce qui est considéré comme une "valeur non valide".

Exemple 1 - strcpy

[7.21.2.3] ne dit que ceci :

El strcpy copie la chaîne de caractères pointée par s2 (y compris le caractère nul de fin) dans le tableau pointé par s1 . Si la copie a lieu entre des objets qui se chevauchent, le comportement est indéfini.

Il ne mentionne pas explicitement les pointeurs nuls, mais il ne mentionne pas non plus les terminateurs nuls. Au lieu de cela, on déduit de "string pointed to by s2 "que les seules valeurs valides sont des chaînes de caractères (c'est-à-dire des pointeurs vers des tableaux de caractères à terminaison nulle).

En effet, ce schéma se retrouve dans toutes les descriptions individuelles. Quelques autres exemples :

  • [7.6.4.1 (fenv)] stocker l'environnement actuel en virgule flottante dans le objet pointé vers par envp

  • [7.12.6.4 (frexp)] stocker l'entier dans l'int objet pointé vers par exp

  • [7.19.5.1 (fclose)] le site stream pointé vers par stream

Exemple 2 - printf

[7.19.6.1] dit ceci à propos de %p :

p - L'argument est 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.

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 vers un objet. Il s'agit donc d'un comportement défini.


1. A moins qu'un auteur de normes ne se manifeste, ou que nous trouvions quelque chose de similaire à une <a href="http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf" rel="noreferrer">raisonnement </a>document qui clarifie les choses.

0 votes

Les commentaires ne sont pas destinés à une discussion approfondie ; cette conversation a été déplacé vers le chat .

1 votes

"mais il ne mentionne pas les terminaisons nulles" est faible dans l'exemple 1 - strcpy, comme le dit la spécification, "copie les terminaisons nulles". chaîne de caractères ". chaîne de caractères est explicitement défini comme ayant un caractère nul .

1 votes

@chux - C'est un peu ce que je veux dire - il faut savoir déduire ce qui est valide/invalide à partir du contexte, plutôt que de supposer que la liste de 7.1.4 est exhaustive. (Cependant, l'existence de cette partie de ma réponse avait un peu plus de sens dans le contexte de commentaires qui ont depuis été supprimés, arguant que strcpy était un contre-exemple).

20voto

Dror K. Points 1194

La réponse courte

Oui . Impression de pointeurs nuls avec la fonction %p Le spécificateur de conversion a un comportement non défini. Cela dit, je n'ai pas connaissance d'une implémentation conforme existante qui se comporterait mal.

La réponse s'applique à toutes les normes C (C89/C99/C11).


La longue réponse

El %p Le spécificateur de conversion attend un argument de type pointeur vers void, la conversion du pointeur en caractères imprimables est définie par l'implémentation. Il n'est pas précisé qu'un pointeur nul est attendu.

L'introduction aux fonctions de la bibliothèque standard indique que les pointeurs nuls en tant qu'arguments des fonctions (de la 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 invalide (comme [...] un pointeur nul, [...] le comportement est indéfini.

Exemples de fonctions (de la bibliothèque standard) qui attendent des pointeurs nuls comme arguments valides :

  • fflush() utilise un pointeur nul pour vider "tous les flux" (qui s'appliquent).
  • freopen() utilise un pointeur nul pour indiquer le fichier "actuellement associé" au flux.
  • snprintf() permet de passer un pointeur nul lorsque 'n' est égal à zéro.
  • realloc() utilise un pointeur nul 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() Pour les fonctions de la bibliothèque standard, il est logique d'autoriser le passage d'un pointeur nul lorsque 'n' est égal à zéro, mais ce n'est pas le cas pour d'autres fonctions (de la bibliothèque standard) qui autorisent un 'n' égal à zéro. Par exemple : memcpy() , memmove() , strncpy() , memset() , memcmp() .

Elle est non seulement spécifiée dans l'introduction à la bibliothèque standard, mais aussi une fois de plus 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 à cette fonction. Sauf indication contraire explicite dans la description d'une fonction particulière du présent paragraphe, les arguments des pointeurs lors d'un tel appel doivent toujours avoir des valeurs valides, comme décrit à l'article 7.1.4.


Est-ce intentionnel ?

Je ne sais pas si l'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 pour les fonctions de la bibliothèque standard, elle spécifie ensuite explicitement les cas où un pointeur nul est un argument valide (snprintf, free, etc.), puis elle répète une fois de plus que les arguments doivent être valides même dans les cas zéro '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 que de telles choses ne soient pas définies.

0 votes

Les commentaires ne sont pas destinés à une discussion approfondie ; cette conversation a été déplacé vers le chat .

0 votes

La deuxième citation que vous mentionnez est tout à fait inapplicable, puisqu'elle traite du passage de tableaux en conjonction avec l'option size_t (c'est ce à quoi se réfère "un tel appel"). Notre tentative d'impression du pointeur nul n'est pas un tel appel et il n'est pas valable de généraliser les règles pour ces appels particuliers à toute fonction prenant un pointeur.

0 votes

@JeroenMostert Dois-je supposer qu'en ne disant rien sur la première citation, vous êtes d'accord avec ce qu'elle dit ? BTW, vous êtes le bienvenu sur le chat ^.

-1voto

supercat Points 25534

Les auteurs de la norme C n'ont pas cherché à dresser une liste exhaustive de toutes les exigences comportementales qu'une implémentation doit satisfaire pour être adaptée à un usage particulier. Ils s'attendaient plutôt à ce que les personnes qui écrivent des compilateurs fassent preuve d'un certain bon sens, que la norme l'exige ou non.

La question de savoir si quelque chose invoque l'UB est rarement utile en soi. Les vraies questions d'importance sont :

  1. Quelqu'un qui essaie d'écrire un compilateur de qualité doit-il le faire se comporter de manière prévisible ? Pour le scénario décrit, la réponse est clairement oui.

  2. Les programmeurs devraient-ils être en droit de s'attendre à ce que des compilateurs de qualité pour tout ce qui ressemble à des plateformes normales se comportent de manière prévisible ? Dans le scénario décrit, je dirais que la réponse est oui.

  3. Certains auteurs de compilateurs obtus pourraient-ils élargir l'interprétation de la norme de manière à justifier un comportement bizarre ? J'espère que non, mais je ne l'exclurais pas.

  4. Les compilateurs de désinfection doivent-ils se plaindre de ce comportement ? Cela dépendrait du niveau de paranoïa de leurs utilisateurs ; un compilateur d'assainissement ne devrait probablement pas se plaindre par défaut d'un tel comportement, mais peut-être fournir une option de configuration pour le cas où des programmes seraient portés sur des compilateurs "intelligents"/dumb qui se comportent bizarrement.

Si une interprétation raisonnable de la norme implique qu'un comportement est défini, mais que certains auteurs de compilateurs étirent l'interprétation pour justifier le contraire, est-ce vraiment important ce que dit la norme ?

0 votes

1. Il n'est pas rare que les programmeurs trouvent que les hypothèses faites par les optimiseurs modernes/agressifs sont en contradiction avec ce qu'ils considèrent comme "raisonnable" ou "de qualité". 2. Lorsqu'il s'agit d'ambiguïtés dans la spécification, il n'est pas rare que les implémenteurs soient en désaccord quant aux libertés qu'ils peuvent prendre. 3. Lorsqu'il s'agit de membres du comité de normalisation du langage C, même eux ne sont pas toujours d'accord sur l'interprétation "correcte", et encore moins sur ce qu'il faut faire. debe être. Compte tenu de ce qui précède, quelle interprétation raisonnable devrions-nous suivre ?

6 votes

Répondre à la question "ce morceau de code particulier invoque-t-il ou non UB" par une dissertation sur ce que vous pensez de l'utilité d'UB ou de la façon dont les compilateurs devraient se comporter est une piètre tentative de réponse, d'autant plus que vous pouvez copier-coller ceci comme réponse à presque tous les cas de figure. cualquier question sur un UB particulier. Pour répondre à votre fleur de rhétorique : oui, ce que dit la norme est vraiment important, peu importe ce que certains auteurs de compilateurs font ou ce que vous pensez d'eux pour cela, car la norme est le point de départ des programmeurs et des auteurs de compilateurs.

1 votes

@JeroenMostert : La réponse à "Est-ce que X invoque un comportement non défini" dépendra souvent de ce que l'on entend par cette question. Si un programme est considéré comme ayant un comportement indéfini si la norme n'impose aucune exigence sur le comportement d'une implémentation conforme, alors presque tous les programmes invoquent UB. Les auteurs de la norme autorisent clairement les implémentations à se comporter de manière arbitraire si un programme imbrique les appels de fonction trop profondément, tant qu'une implémentation peut traiter correctement au moins un texte source (éventuellement artificiel) qui exerce les limites de traduction de la norme.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X