37 votes

Est-ce que delete est autorisé à modifier son paramètre?

Dans une réponse, https://stackoverflow.com/a/704568/8157187, il ya une citation de Stroustrup:

C++ permet explicitement de mise en œuvre de supprimer à zéro un lvalue opérande, et j'avais espéré que les implémentations ferais, mais cette idée ne semble pas avoir la cote auprès des maîtres d'œuvre.

Cependant, je n'ai pas réussi à trouver cette déclaration explicite dans la norme. Il y a une partie de l'actuel projet de norme (N4659), que l'on peut interpréter de cette façon:

6.7:

Lorsque la fin de la durée d'une région de stockage est atteinte, le les valeurs de tous les pointeurs représentant de l'adresse d'une partie de la région de stockage deviennent de pointeur non valide valeurs (6.9.2). Indirection par le biais d'un pointeur invalide valeur et passage d'une valeur de pointeur non valide pour une fonction de libération avoir un comportement indéfini. Toute autre utilisation d'un valeur de pointeur non valide a la mise en œuvre définies par le comportement.

Note: Certaines implémentations peuvent définir que la copie d'une valeur de pointeur non valide provoque une générés par le système d'exécution de la faute

Ainsi, après un delete ptr;, ptrs'devient une valeur de pointeur non valide, et à l'aide de cette valeur est mise en œuvre définies par le comportement. Toutefois, cela ne veut pas dire qu' ptrs'valeur peut changer.

Ce pourrait être une question philosophique, comment peut-on décider qu'une valeur a changé, si l'on ne peut pas utiliser sa valeur?

6.9:

Pour un objet quelconque (autre qu'une classe de base sous-objet) de façon triviale copiable type T, si l'objet est titulaire d'une valeur valide de type T, le sous-jacent octets (4.4) faisant l'objet peut être copié dans un tableau de char, unsigned char, ou std::byte (21.2.1).43 Si l' le contenu de ce tableau est copié en arrière dans l'objet, l'objet est par la suite tenir sa valeur d'origine.

Donc, il semble, qu'il est valide d' memcpy d'une valeur de pointeur non valide dans un char array (selon la déclaration est "plus fort", de 6,7 6,9. Pour moi, 6.9 semble plus fort).

De cette façon, je peux détecter, que la valeur du pointeur a été modifié par delete: memcpy la valeur du pointeur avant et après l' delete char de la matrice, puis de les comparer.

Donc, si je comprends bien, 6.7 n'accorde pas qu' delete est autorisé à modifier son paramètre.

Est de supprimer le droit de modifier ses paramètres?

Découvrez les commentaires ici: https://stackoverflow.com/a/45142972/8157187


Voici un peu de chances, mais encore possibles dans le monde réel de code, où cette matière:

SomeObject *o = ...; // We have a SomeObject
// This SomeObject is registered into someHashtable, with its memory address
// The hashtable interface is C-like, it handles opaque keys (variable length unsigned char arrays)

delete o;

unsigned char key[sizeof(o)];
memcpy(key, &o, sizeof(o)); // Is this line OK? Is its behavior implementation defined?
someHashtable.remove(key, sizeof(key)); // Remove o from the hashtable

Bien sûr, cet extrait peut être réorganisées, de sorte qu'il devient un sûrement un code valide. Mais la question est: est-ce un code valide?


Voici une relative train de la pensée: supposons qu'une mise en œuvre n'définir ce que note de bas de page décrit:

la copie d'une valeur de pointeur non valide provoque une générés par le système d'exécution de la faute

6.9 les garanties que j'ai peut - memcpy() de la valeur. Même une réserve non valide. Donc, dans cette théorie de la mise en œuvre, quand j' memcpy() de la valeur de pointeur non valide (ce qui devrait lui succéder, 6.9 garantit que), dans un sens, je n'utilise pas la valeur de pointeur non valide, mais seulement ses sous-jacent octets (car il permettrait de générer une faute d'exécution, et de 6,9 ne le permet pas), afin de 6,7 ne s'applique pas.

13voto

Matt McNabb Points 14273

Avant la suppression, ptr's la valeur est valide. Après la suppression, la valeur n'est pas valide. Par conséquent, la valeur a changé. Les valeurs valides et non valides et les valeurs sont mutuellement exclusifs, une valeur ne peut pas être à la fois valides et non valides.

Votre question a une idée fausse; vous faites l'amalgame entre ces deux concepts différents:

  • La valeur d'une variable
  • La représentation d'une variable en mémoire.

Il n'y a pas un one-to-one correspondance entre ces deux choses. La même valeur peut avoir plusieurs représentations, et la même représentation peut correspondre à des valeurs différentes.


Je pense que l'essentiel de votre question est: peut - delete ptr; modifier la représentation de l' ptr?. La réponse est "Oui". Vous pourriez memcpy supprimés pointeur sur un tableau de char, inspecter les octets, et de les trouver tous à une valeur zéro octets (ou autre chose). Ce qui est couvert dans le standard C++14 [de base.stc.dynamique.libération de la mémoire]/4 (ou C++17 [de base.stc]/4):

Toute autre utilisation d'une valeur de pointeur non valide a la mise en œuvre définies par le comportement.

C'est définis par l'implémentation et la mise en œuvre pourrait définir que inspecter les octets donne octets avec la valeur zéro.


Votre extrait de code s'appuie sur la mise en œuvre définies par le comportement. "Le code est valide" n'est pas la terminologie utilisée par la Norme, mais le code peut ne pas supprimer la destinée de l'élément de la table de hachage.

Comme évoqué par l'Stroustrup, c'est intentionnel de décision de conception. Un exemple d'utilisation serait un compilateur en mode debug paramètre supprimé des pointeurs vers une représentation particulière, de sorte qu'il peut soulever une erreur d'exécution si la suppression du pointeur est utilisé par la suite. Voici un exemple de ce principe en action pour les pointeurs non initialisés.

Note historique: En C++11 cette affaire était pas défini, plutôt que définis par l'implémentation. Ainsi, le comportement de l'supprimés à l'aide d'un pointeur est identique au comportement de l'utilisation d'un pointeur non initialisé. Dans le langage C, la libération de la mémoire est définie comme étant de mettre tous les pointeurs pour que la mémoire dans le même état qu'un pointeur non initialisé est.

2voto

Barry Points 45207

delete est défini dans [expr.delete] pour appeler une fonction de désallocation, et les fonctions de désallocation sont définies dans [basic.stc.dynamic.deallocation] comme suit:

Chaque fonction de désallocation doit renvoyer void et son premier paramètre doit être void* .

Étant donné que toutes les fonctions de désallocation obtiennent void* , et non pas void*& , il n’existe aucun mécanisme leur permettant de modifier leurs paramètres.

2voto

stefan bachert Points 4698

Le contexte dans lequel vous l'avez trouvé la déclaration de Stroustrup, est disponible sous Stroustrup, supprimer zéro

Stroustrup laissez-vous envisager

delete p;
// ...
delete p;

Après la première supprimer, pointeur p est non valide. La deuxième suppression est mauvais, mais il n'aurait pas d'effet si p est définie à 0 après la première supprimer.

Stroustrups idée était de le compiler en tant que quelque chose comme

delete p; p = 0;
// ...
delete p;

supprimer lui-même n'est pas en mesure de réinitialiser le pointeur puisqu'il passait void * mais pas void *&

Cependant, je trouve zéro de p n'aide pas beaucoup, car ils peuvent exister d'autres copies de ce pointeur qui peut accidentellement supprimé, trop. Une meilleure solution est d'utiliser les différents type de smartpointers.

Spec 6.7

Dit que toute pointeur avec l'adresse du pointeur p sera pas valide (dans une mise en œuvre définies) après une suppression p. Il ne dit rien sur la modification de l'adresse de pointeur, et ce n'est autorisée, ni interdite.

Spec 6.9

Le prerequisit de 6.9 est un objet (valide ou non). Cette spécification ne s'applique pas ici puisque p (adresse) n'est plus valable après le supprimer et thatfore ne pointe PAS vers un objet. Donc, il n'y a pas de contradiction, et toute discussion, même si de 6,7 6,9 est plus fort n'est pas valide.

La spécification exige également de copier les octets de retour à l'objet original, emplacement, votre code n'est pas, et n'aurait pas pu trop, parce que l'objet d'origine a été supprimé.


Cependant, je ne vois aucune raison pour pack un pointeur d'adresse dans un tableau de char et de le transmettre. Et les pointeurs ont toujours la même taille dans une certaines mise en œuvre. Votre code est juste une version lourde de:

SomeObject *o = ...;

void *orgO = o;
delete o;

someHashtable.remove(orgO);
// someHashtable.remove(o); o might be set to 0

Toutefois, ce code semble toujours étrange. Pour obtenir un objet de la table de hachage, vous avez besoin l'un pointeur vers cet objet. Pourquoi ne pas utiliser directement le pointeur directement??

Une table de hachage doit aider à trouver des objets par certains invariants valeurs des objets. Ce n'est pas votre application de la table de hachage

Avez-vous l'intention d'avoir une liste de toutes les instances de l' SomeObject?

Votre code n'est pas valide cause, selon Stroustrup, le compilateur est autorisé à p set à zéro. Si ce qui s'est passé votre code crash

2voto

philipxy Points 1340

Indirection par l'intermédiaire d'un pointeur invalide valeur et en passant un pointeur non valide de la valeur à une fonction de libération avoir un comportement indéfini. Toute autre utilisation d'une valeur de pointeur non valide a la mise en œuvre définies par le comportement.

Ainsi, après un delete ptr;, ptrs'devient une valeur de pointeur non valide, et à l'aide de cette valeur est mise en œuvre définies par le comportement.

La norme est à dire que la valeur du pointeur passé "devient non valide", c'est à dire son statut a changé, de sorte que certains appels d'indéfini et la mise en œuvre peut les traiter différemment.

La langue n'est pas très clair, mais voici le contexte:

6.7 durée de Stockage
4 Lorsque la fin de la durée d'une région de stockage est atteinte, les valeurs de tous les pointeurs représentant de l'adresse d'une partie de ladite région de stockage deviennent de pointeur non valide valeurs (6.9.2). Indirection par l'intermédiaire d'un pointeur invalide valeur et en passant un pointeur non valide de la valeur à une fonction de libération avoir un comportement indéfini. Toute autre utilisation d'une valeur de pointeur non valide a la mise en œuvre définies par le comportement.

6.9.2 les types Composés
Chaque valeur de type pointeur est l'un des suivants:
(3.1) - un pointeur vers un objet ou une fonction (le pointeur est dit pour pointer vers l'objet ou la fonction), ou
(3.2) - un pointeur passé la fin d'un objet (8.7), ou
(3.3) - la valeur de pointeur null (7.11) pour ce type, ou
(3.4) - une valeur de pointeur non valide.

Il est des valeurs de type pointeur qui sont ou ne sont pas invalides, et qu'ils "deviennent" ainsi, selon les progrès de l'excution d'un programme sur le C++ machine abstraite.

La norme n'est pas de parler de changement à ce que la valeur est détenue par la variable/objet adressée par une lvalue ou des changements à l'association d'un symbole avec une valeur.

C++ permet explicitement de mise en œuvre de supprimer à zéro un lvalue opérande, et j'avais espéré que les implémentations ferais, mais cette idée ne semble pas avoir la cote auprès des maîtres d'œuvre.

Séparément à partir de cela, Stroustrup est à dire que si un opérande expression était modifiable lvalue, c'est à dire un opérande expression est l'adresse d'une variable/objet contenant un pointeur valeur qui est passée, d'après lequel le statut de cette valeur est "invalide", puis la mise en œuvre peut définir la valeur par variable/objet à zéro.

Toutefois, cela ne veut pas dire qu' ptrs'valeur peut changer.

Stroustrup est informelle par parler de ce que d'une mise en œuvre peut faire. La norme définit la façon dont un résumé C++ machine peut/ne peut pas/peut se comporter. Ici Stroustrup parle d'une hypothétique mise en œuvre qui ressemble à celle de la machine. Un "ptrs'valeur" est "autorisé à changer" parce que le défini et l'indéfini comportement ne permet pas de savoir ce que la valeur de la libération, et la mise en œuvre définies par le comportement peut être n'importe quoi, donc il se peut que la variable/objet contient une valeur différente.

Il n'a pas de sens de parler d'une modification de la valeur. Vous ne pouvez pas "zéro" d'une valeur, vous pouvez remettre à zéro une variable/objet, et c'est ce que nous voulons dire quand nous disons "zéro" une lvalue--à zéro la variable/objet références/identifie. Même si vous tirez sur "zéro" pour inclure, en l'associant à une nouvelle valeur à un nom ou littéral, la mise en œuvre peut le faire, parce que vous ne pouvez pas "utiliser" la valeur au moment de l'exécution par le nom ou littéral pour savoir si il est toujours associé à la même valeur.

(Cependant, puisque tout ce qu'on peut faire avec une valeur est "utiliser" dans un programme en passant une lvalue l'identification d'une variable/objet en le tenant à un opérateur ou par le passage d'une référence ou une constante indiquant à l'opérateur concerné, et un opérateur peut agir comme si différente de la valeur a été passée, je pense que l'on peut raisonnablement de manière informelle, sans soin capture que "l'évolution des valeurs de la valeur" dans la mise en œuvre.)

Si le contenu de ce tableau est copié en arrière dans l'objet, l'objet doit ensuite tenir sa valeur d'origine.

Mais la copie est à l'utiliser afin de copier la mise en œuvre est définie une fois qu'il est "invalide". Donc appeler un programme qui serait normalement copier la mise en œuvre est définie. Cela ressort clairement de la note de bas de page qui donne l'exemple

Certaines implémentations peuvent définir que la copie d'une valeur de pointeur non valide provoque une générés par le système d'exécution de la faute

Rien n'est ce qu'il fait normalement comme des non défini/mise en oeuvre-comportement défini. Nous utilisons le comportement normal pour déterminer une séquence de changements pour une machine abstraite, et si une mise en œuvre définies par le changement d'état se pose alors les choses agissent comme la mise en œuvre définit à l'acte, pas la façon dont ils le font normalement. Malheureusement, le sens de "l'utilisation" d'une valeur n'est pas clair. Je ne sais pas pourquoi vous pensez que 6.9 "garanties" quelque chose de plus ou moins memcpy que partout ré quoi que ce soit, qui, après un non défini/mise en œuvre définies par l'état n'est rien.

1voto

Serge Points 4415

Pour qu'une fonction de suppression de mettre à jour le pointeur, il faut connaître l'adresse de ce pointeur, et aussi faire la mise à jour. Cela nécessiterait plus de mémoire, quelques-uns des opérations supplémentaires et une prise en charge du compilateur. L'air plus ou moins trivial dans votre exemple.

Maintenant, imaginez une chaîne de fonctions qui passe le pointeur de chacun des autres dans les arguments et seul le dernier sera vraiment supprimer. Qui pointeurs de mettre à jour dans un tel cas? Le dernier? Tous? Pour ces derniers, on aurait besoin de créer une dynamique de listes de pointeurs:

Objec *o = ...
handle(o);
void handle(Object *o){
   if (deleteIt) doDelete(0);
   else doSomethingElseAndThenPossiblyDeleteIt(o);
}
void doDelete(Object *o) {
    delete o;
}

Donc, du point de vue philosophique, si la supprimer serait autorisé à modifier son paramètre, ce serait ouvrir une boîte de réchauffe la réduction de l'efficacité du programme. Donc, il n'est pas permis et j'espère qu'il ne sera jamais. Le comportement non défini est probablement la chose la plus naturelle dans ces cas.

Comme pour le contenu de la mémoire, malheureusement j'ai vu trop d'erreurs lorsqu'une mémoire supprimée est remplacée une fois que le pointeur a été supprimé. Et... cela fonctionne bien jusqu'à il y a un moment. Puisque la mémoire est marqué comme libre il se réutilisé par d'autres objets, éventuellement, avec de très inintéressant conséquences et beaucoup de débogage. Donc, du point de vue philosophique de nouveau, c++ n'est pas une langue facile à programmer avec. Il y a d'autres outils qui pourraient prendre ces questions, sans langue de soutien.

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