web-dev-qa-db-fra.com

Est-il sûr de supprimer un pointeur vide?

Supposons que j'ai le code suivant:

void* my_alloc (size_t size)
{
   return new char [size];
}

void my_free (void* ptr)
{
   delete [] ptr;
}

Est-ce sûr? Ou doit-il être converti en ptr en char* avant la suppression?

87
An̲̳̳drew

Cela dépend de "sûr". Cela fonctionnera généralement parce que les informations sont stockées avec le pointeur sur l'allocation elle-même, afin que le désallocateur puisse les renvoyer au bon endroit. En ce sens, il est "sûr" tant que votre allocateur utilise des balises de limites internes. (Beaucoup le font.)

Cependant, comme mentionné dans d'autres réponses, la suppression d'un pointeur vide n'appellera pas de destructeurs, ce qui peut être un problème. En ce sens, ce n'est pas "sûr".

Il n'y a aucune bonne raison de faire ce que vous faites comme vous le faites. Si vous souhaitez écrire vos propres fonctions de désallocation, vous pouvez utiliser des modèles de fonction pour générer des fonctions avec le type correct. Une bonne raison de le faire est de générer des allocateurs de pool, qui peuvent être extrêmement efficaces pour des types spécifiques.

Comme mentionné dans d'autres réponses, c'est comportement indéfini en C++. En général, il est bon d'éviter les comportements indéfinis, bien que le sujet lui-même soit complexe et rempli d'opinions contradictoires.

22
Christopher

La suppression via un pointeur void n'est pas définie par la norme C++ - voir section 5.3.5/3:

Dans la première alternative (supprimer un objet), si le type statique de l'opérande est différent de son type dynamique, le type statique doit être une classe de base du type dynamique de l'opérande et le type statique doit avoir un destructeur virtuel ou le comportement n'est pas défini . Dans la deuxième alternative (tableau de suppression) si le type dynamique de l'objet à supprimer diffère de son type statique, le comportement n'est pas défini.

Et sa note de bas de page:

Cela implique qu'un objet ne peut pas être supprimé à l'aide d'un pointeur de type void * car il n'y a pas d'objets de type void

.

137
anon

Ce n'est pas une bonne idée et ce n'est pas quelque chose que vous feriez en C++. Vous perdez vos informations de type sans raison.

Votre destructeur ne sera pas appelé sur les objets de votre tableau que vous supprimez lorsque vous l'appelez pour des types non primitifs.

Vous devriez plutôt remplacer new/delete.

La suppression du vide * libérera probablement votre mémoire correctement par hasard, mais c'est faux car les résultats ne sont pas définis.

Si pour une raison inconnue, vous devez stocker votre pointeur dans un vide * puis le libérer, vous devez utiliser malloc et free.

24
Brian R. Bondy

La suppression d'un pointeur vide est dangereuse car les destructeurs ne seront pas appelés sur la valeur vers laquelle ils pointent réellement. Cela peut entraîner des fuites de mémoire/ressources dans votre application.

13
JaredPar

La question n'as pas de sens. Votre confusion peut être due en partie au langage bâclé que les gens utilisent souvent avec delete:

Vous utilisez delete pour détruire un objet qui a été alloué dynamiquement. Pour ce faire, vous formez une expression de suppression avec un pointeur vers cet objet . Vous ne "supprimez jamais un pointeur". Ce que vous faites vraiment, c'est "supprimer un objet identifié par son adresse".

Nous voyons maintenant pourquoi la question n'a pas de sens: un pointeur vide n'est pas "l'adresse d'un objet". C'est juste une adresse, sans aucune sémantique. Il peut provenir de l'adresse d'un objet réel, mais cette information est perdue, car elle a été codée dans le type du pointeur d'origine. La seule façon de restaurer un pointeur d'objet est de replacer le pointeur vide sur un pointeur d'objet (ce qui nécessite que l'auteur sache ce que signifie le pointeur). void lui-même est un type incomplet et donc jamais le type d'un objet, et un pointeur vide ne peut jamais être utilisé pour identifier un objet. (Les objets sont identifiés conjointement par leur type et leur adresse.)

7
Kerrek SB

Si vous devez vraiment le faire, pourquoi ne pas couper l'homme du milieu (les opérateurs new et delete) et appeler directement les globaux operator new Et operator delete? (Bien sûr, si vous essayez d'instrumenter les opérateurs new et delete, vous devez réellement réimplémenter operator new Et operator delete.)

void* my_alloc (size_t size)
{
   return ::operator new(size);
}

void my_free (void* ptr)
{
   ::operator delete(ptr);
}

Notez qu'à la différence de malloc(), operator new Lance std::bad_alloc En cas d'échec (ou appelle le new_handler S'il est enregistré).

5
bk1e

Parce que char n'a pas de logique destructrice spéciale. Cela ne fonctionnera pas.

class foo
{
   ~foo() { printf("huzza"); }
}

main()
{
   foo * myFoo = new foo();
   delete ((void*)foo);
}

L'ctor ne sera pas appelé.

5
radu

Beaucoup de gens ont déjà commenté que non, il n'est pas sûr de supprimer un pointeur vide. Je suis d'accord avec cela, mais je voulais également ajouter que si vous travaillez avec des pointeurs void afin d'allouer des tableaux contigus ou quelque chose de similaire, vous pouvez le faire avec new afin que vous puissiez utilisez delete en toute sécurité (avec, ahem, un peu de travail supplémentaire). Cela se fait en allouant un pointeur vide à la région de mémoire (appelée "arène"), puis en fournissant le pointeur à l'arène à new. Voir cette section dans la FAQ C++ . Il s'agit d'une approche courante pour implémenter des pools de mémoire en C++.

5
Paul Morie

Si vous souhaitez utiliser void *, pourquoi n'utilisez-vous pas simplement malloc/free? new/delete est plus qu'une simple gestion de la mémoire. Fondamentalement, new/delete appelle un constructeur/destructeur et il se passe plus de choses. Si vous utilisez simplement des types intégrés (comme char *) et les supprimez via void *, cela fonctionnerait mais ce n'est toujours pas recommandé. L'essentiel est d'utiliser malloc/free si vous souhaitez utiliser void *. Sinon, vous pouvez utiliser les fonctions de modèle pour votre commodité.

template<typename T>
T* my_alloc (size_t size)
{
   return new T [size];
}

template<typename T>
void my_free (T* ptr)
{
   delete [] ptr;
}

int main(void)
{
    char* pChar = my_alloc<char>(10);
    my_free(pChar);
}
5
young

Si vous voulez juste un tampon, utilisez malloc/free. Si vous devez utiliser new/delete, considérez une classe wrapper triviale:

template<int size_ > struct size_buffer { 
  char data_[ size_]; 
  operator void*() { return (void*)&data_; }
};

typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer

OpaqueBuffer* ptr = new OpaqueBuffer();

delete ptr;
0
Sanjaya R

Pour le cas particulier de car.

char est un type intrinsèque qui n'a pas de destructeur spécial. L'argument des fuites est donc théorique.

sizeof (char) est généralement un donc il n'y a pas non plus d'argument d'alignement. Dans le cas d'une plate-forme rare où la taille de (char) n'est pas une, ils allouent suffisamment de mémoire pour leur caractère. L'argument d'alignement est donc également théorique.

malloc/free serait plus rapide dans ce cas. Mais vous perdez std :: bad_alloc et devez vérifier le résultat de malloc. Il pourrait être préférable d'appeler les opérateurs globaux new et delete car il contourne l'homme intermédiaire.

0
rxantos

J'ai utilisé void *, (alias types inconnus) dans mon framework pendant la réflexion de code et d'autres exploits d'ambiguïté, et jusqu'à présent, je n'ai eu aucun problème (fuite de mémoire, violations d'accès, etc.) d'aucun compilateur. Seuls les avertissements dus au fonctionnement non standard.

Il est parfaitement logique de supprimer un inconnu (void *). Assurez-vous simplement que le pointeur suit ces instructions, ou il peut ne plus avoir de sens:

1) Le pointeur inconnu ne doit pas pointer vers un type qui a un déconstructeur trivial, et donc lorsqu'il est converti en pointeur inconnu, il ne doit JAMAIS ÊTRE SUPPRIMÉ. Supprimez uniquement le pointeur inconnu APRÈS l'avoir replacé dans le type ORIGINAL.

2) L'instance est-elle référencée comme un pointeur inconnu dans la mémoire liée à la pile ou liée au tas? Si le pointeur inconnu fait référence à une instance de la pile, elle ne doit JAMAIS ÊTRE SUPPRIMÉE!

3) Êtes-vous sûr à 100% que le pointeur inconnu est une région de mémoire valide? Non, alors cela ne devrait JAMAIS ÊTRE SUPPRIMÉ!

En tout, il y a très peu de travail direct qui peut être fait en utilisant un type de pointeur inconnu (void *). Cependant, indirectement, le void * est un grand atout sur lequel les développeurs C++ peuvent compter lorsqu'une ambiguïté des données est requise.

0
zackery.fix

Il n'y a guère de raison de le faire.

Tout d'abord, si vous ne connaissez pas le type des données, et tout ce que vous savez, c'est que c'est void*, alors vous devriez vraiment traiter ces données comme un type blob de données binaires (unsigned char*), et utilisez malloc/free pour y faire face. Cela est parfois nécessaire pour des choses comme les données de forme d'onde et autres, où vous devez faire circuler void* pointe vers C apis. C'est très bien.

Si vous faites connaissez le type des données (c'est-à-dire qu'elles ont un ctor/dtor), mais pour une raison quelconque vous vous êtes retrouvé avec un void* pointeur (pour quelque raison que ce soit) alors vous devriez vraiment le restituer au type que vous connaissez, et appeler delete dessus.

0
bobobobo