Tout d'abord, pour clarifier, je ne parle pas de la dereferrisation des pointeurs invalides!
Considérons les deux exemples suivants.
Exemple 1
typedef struct { int *p; } T;
T a = { malloc(sizeof(int) };
free(a.p); // a.p est maintenant indéterminé?
T b = a; // Accès via un type non caractère?
Exemple 2
void foo(int *p) {}
int *p = malloc(sizeof(int));
free(p); // p est maintenant indéterminé?
foo(p); // Accès via un type non caractère?
Question
Est-ce que l'un des deux exemples ci-dessus induit un comportement indéfini?
Contexte
Cette question est posée en réponse à cette discussion. La suggestion était que, par exemple, les arguments de pointeur peuvent être passés à une fonction via des registres de segment x86, ce qui pourrait provoquer une exception matérielle.
D'après la norme C99, nous apprenons ce qui suit (soulignement de ma part):
[3.17] valeur indéterminée - soit une valeur non spécifiée soit une représentation de piège
et ensuite:
[6.2.4 p2] La valeur d'un pointeur devient indéterminée lorsque l'objet pointé atteint la fin de sa durée de vie.
et ensuite:
[6.2.6.1 p5] Certaines représentations d'objet ne doivent pas représenter une valeur du type d'objet. Si la valeur stockée d'un objet a une telle représentation et est lue par une expression lvalue qui n'a pas de type caractère, le comportement est indéfini. Si une telle représentation est produite par un effet secondaire qui modifie tout ou partie de l'objet par une expression lvalue qui n'a pas de type caractère, le comportement est indéfini. Une telle représentation est appelée une représentation de piège.
En prenant tout ceci en compte, quelles restrictions avons-nous sur l'accès aux pointeurs vers des objets "morts"?
Addendum
Alors que j'ai cité la norme C99 ci-dessus, je serais intéressé de savoir si le comportement diffère dans l'une des normes C++.
3 votes
Vous avez cité la norme de manière excellente - ces mots me donnent clairement entendre que l'utilisation d'un pointeur invalide de quelque manière que ce soit, même sans le déréférencer, invoque un comportement indéfini.
0 votes
Je ne vois pas d'où cela devrait venir. Tant que vous passez le pointeur autour, rien ne se passe. bien sûr c'est évident, que cela n'a pas de sens, car vous ne pouvez pas utiliser ce pointeur de toute façon, mais le passer autour est pratiquement la même chose que d'avoir un pointeur non initialisé.
1 votes
@Devolus : Oui, c'était aussi mon intuition. Mais la norme semble relativement non ambiguë. Et AProgrammer a fait remarquer (dans la discussion liée) que si les registres de segment sont impliqués, cela pourrait vraiment entraîner une exception matérielle.
0 votes
@Devolus, ce que nous essayons de comprendre, c'est : "est-ce que le fait de le transmettre est sécurisé ?"
0 votes
Le
free
ne modifie pas son argument. Le pointeur passé àfree
pointe toujours vers le même emplacement après coup. L'appel àfree
informe simplement la bibliothèque standard que l'objet n'est plus "utilisé" et que l'espace à cet emplacement peut être réutilisé. Ce n'est pas la même chose que l'objet "atteignant la fin de sa durée de vie", ce qui se produit pour les objets sur la pile.3 votes
@willj: C'est exact. Mais néanmoins, la norme nous dit que le pointeur est maintenant indéterminé.
0 votes
C++ a récemment rendu cette définition dépendante de l'implémentation, voir DR 1438, car elle ne piégera en réalité pas sur tous les systèmes
0 votes
Le pointeur est indéterminé si l'objet a atteint la fin de sa durée de vie.. où est-il dit que 'free' cause un objet à 'atteindre la fin de sa durée de vie'? Comme je peux créer ma propre implémentation de
malloc
etfree
, je suppose qu'une implémentation n'est pas autorisée à leur accorder un traitement spécial.1 votes
"Faire son propre"
malloc
etfree
invoque déjà un comportement indéfini. 7.1.3 : "Si le programme déclare ou définit un identificateur dans un contexte où il est réservé (autre que ce qui est autorisé par 7.1.4), ou définit un identificateur réservé comme nom de macro, le comportement est indéfini."0 votes
@Oli : ah, alors je me corrige ;)
0 votes
@R.. : Je voulais dire que je peux créer ma propre fonction
customMalloc()
etcustomFree()
- auquel cas la durée de vie de l'objet ne serait pas affectée.3 votes
@willj, il ne s'agit pas de modifier cette valeur. Il est très probable que le pointeur ait toujours la même valeur. Cependant, si cette valeur est copiée quelque part, elle peut passer par un registre de pointeur spécial (par exemple le registre de segment dans x86) où le matériel pourrait provoquer un piège en raison de l'invalidité du pointeur.
0 votes
@Oli Charlesworth Je pense que vous lisez un peu trop "entre les lignes". La norme indique qu'un pointeur est indéterminé si l'objet pointé atteint la fin de sa durée de vie. Mais cela est cité dans 6.2.4, le chapitre sur la durée de stockage. On pourrait soutenir et dire que le texte cité fait seulement référence à un pointeur vers un objet ayant atteint la fin de sa portée, puisque ce chapitre commence en déclarant
"L'espace alloué est décrit dans 7.22.3"
. Autrement dit, l'espace alloué est un cas spécial où 6.2.4 ne s'applique pas nécessairement.0 votes
Mais malheureusement, il n'y a pas d'informations utiles dans 7.22.3 concernant le sujet, ou ce qui se passe avec un pointeur lorsque vous le passez à free() - qu'il devienne formellement indéterminé ou non.
0 votes
@Lundin: Hmm, ce n'est pas ainsi que je l'interprète. Je ne considère pas le stockage alloué comme un cas spécial, il est simplement décrit dans une section séparée pour plus de commodité. Cependant, si votre interprétation est correcte, nous pourrions simplement réécrire mes exemples pour utiliser des pointeurs vers des objets automatiques qui ont expiré...
0 votes
@OliCharlesworth Il est loin d'être évident de l'interpréter. Après une deuxième lecture de C11 6.2.4, j'ai trouvé que le chapitre définit la durée de vie des objets statiques et automatiques (et pour le stockage des threads en C11), mais pas pour les objets "alloués". Pourtant, dans C11 7.22.3, il y a une phrase qui indique :
La durée de vie d'un objet alloué s'étend de l'allocation jusqu'à la désallocation.
Cette phrase semble bien s'harmoniser avec le texte que vous avez cité de 6.2.4.0 votes
@Lundin: C'est lorsque l'objet a atteint la fin de sa durée de vie, pas (nécessairement) la fin de sa portée. (La portée est une région du texte du programme sur laquelle un identifiant est visible.)
0 votes
@OliverCharlesworth puis-je suggérer de changer ceci en une question sur le langage C? Puisque C et C++ sont considérablement différents dans ce domaine, cette question deviendrait confuse si des réponses en C++ étaient ajoutées. Il pourrait y avoir un autre fil créé pour la version C++. (La réponse existante en C++ qui a été postée ne répond en fait pas du tout à la question)
0 votes
@MattMcNabb : Bien sûr, si vous voulez. La partie en C++ de la question n'a été ajoutée que comme un addendum...
0 votes
@MattMcNabb Jonathan Wakely a déjà mentionné DR 1438. Utilisation non-déréférencée de pointeurs invalides : "Le Standard actuel indique que toute utilisation d'une valeur de pointeur invalide produit un comportement indéfini (3.7.4.2 [basic.stc.dynamic.deallocation] paragraphe 4). Cela inclut non seulement la déréférenciation du pointeur mais même juste obtenir sa valeur." Rien à ajouter ici.
0 votes
@curiousguy C++ ne définit pas clairement ce qu'est un pointeur invalide ; la quantité de discussions générée sur cette question suggère que ce n'est pas si simple.