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 ptr
être casté char*
avant la suppression?
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 ptr
être casté char*
avant la suppression?
Réponses:
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, de sorte 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 limite 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, il s'agit d' un comportement non dé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.
sizeof(T*) == sizeof(U*)
for all T,U
suggère qu'il devrait être possible d'avoir une void *
implémentation de garbage collector non basée sur un modèle . Mais alors, lorsque le gc doit effectivement supprimer / libérer un pointeur, exactement cette question se pose. Pour le faire fonctionner, vous avez soit besoin de wrappers de destructeurs de fonctions lambda (urgh), soit d'une sorte de "type en tant que données" dynamique qui permet des allers-retours entre un type et quelque chose de stockable.
La suppression via un pointeur void n'est pas définie par le standard 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 (supprimer un tableau) 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
.
NULL
quoi faire une différence pour la gestion de la mémoire d'application?
Ce n'est pas une bonne idée et 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 devez à la place remplacer nouveau / supprimer.
La suppression du void * 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 de moi, vous avez besoin de stocker votre pointeur dans un vide * alors libérez-le, vous devez utiliser malloc et free.
la question n'as pas de sens. Votre confusion peut être en partie due 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 réellement, c'est "supprimer un objet identifié par son adresse".
Maintenant, nous voyons pourquoi la question n'a aucun 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 convertir le pointeur void en un pointeur d'objet (ce qui oblige l'auteur à savoir 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.)
delete
peut être une valeur de pointeur nul, un pointeur vers un objet non-tableau créé par une nouvelle expression précédente , ou un pointeur vers un sous-objet représentant une classe de base d'un tel objet. Sinon, le comportement n'est pas défini. " Donc si un compilateur accepte votre code sans diagnostic, ce n'est rien d'autre qu'un bogue dans le compilateur ...
delete void_pointer
. C'est un comportement indéfini. Les programmeurs ne doivent jamais invoquer un comportement indéfini, même si la réponse semble faire ce que le programmeur voulait faire.
Si vous devez vraiment le faire, pourquoi ne pas couper l'homme du milieu (les new
et delete
opérateurs) et appeler le monde operator new
et operator delete
directement? (Bien sûr, si vous essayez d'instrumenter les opérateurs new
et delete
, vous devez en fait 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 que contrairement à malloc()
, operator new
jette std::bad_alloc
en cas d'échec (ou appelle le new_handler
si on est enregistré).
Parce que char n'a pas de logique destructrice spéciale. CECI ne fonctionnera pas.
class foo
{
~foo() { printf("huzza"); }
}
main()
{
foo * myFoo = new foo();
delete ((void*)foo);
}
L'acteur ne sera pas appelé.
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 n'utilisez que des types intégrés (comme char *) et que vous les supprimez via void *, cela fonctionnerait mais ce n'est toujours pas recommandé. En fin de compte, utilisez 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);
}
Beaucoup de gens ont déjà commenté en disant 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 vides afin d'allouer des tableaux contigus ou quelque chose de similaire, vous pouvez le faire avec new
afin que vous puissiez utiliser en delete
toute sécurité (avec, ahem , un peu de travail supplémentaire). Cela se fait en attribuant un pointeur vide à la région mémoire (appelée «arène»), puis en fournissant le pointeur vers l'arène à nouveau. Consultez cette section dans la FAQ C ++ . Il s'agit d'une approche courante pour l'implémentation de pools de mémoire en C ++.
Il n'y a guère de raison de faire cela.
Tout d'abord, si vous ne connaissez pas le type de données, et tout ce que vous savez c'est que c'est void*
, alors vous devriez vraiment simplement traiter ces données comme un blob sans type de données binaires ( unsigned char*
), et utiliser malloc
/ free
pour les gérer . Cela est parfois nécessaire pour des éléments tels que les données de forme d'onde, etc., où vous devez transmettre des void*
pointeurs vers des apis C. C'est très bien.
Si vous faites connaître le type des données (il a un cteur / dtor), mais pour une raison quelconque vous retrouviez avec un void*
pointeur (pour une raison quelconque vous avez) alors vous devriez vraiment jeter de nouveau le type que vous savez à être , et invoquez- delete
le.
J'ai utilisé void *, (aka 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.) de la part des compilateurs. Uniquement les avertissements dus au fait que l'opération n'est pas standard.
Il est parfaitement logique de supprimer un inconnu (void *). Assurez-vous simplement que le pointeur suit ces instructions, sinon 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É. Ne supprimez le pointeur inconnu qu'APRÈS l'avoir renvoyé 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 au tas? Si le pointeur inconnu fait référence à une instance sur la pile, il ne doit JAMAIS ÊTRE SUPPRIMÉ!
3) Êtes-vous sûr à 100% que le pointeur inconnu est une région de mémoire valide? Non, alors il 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 excellent atout sur lequel les développeurs C ++ peuvent s'appuyer lorsque l'ambiguïté des données est requise.
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;
Pour le cas particulier de char.
char est un type intrinsèque qui n'a pas de destructeur spécial. Les arguments relatifs aux fuites sont donc sans objet.
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ù le sizeof (char) n'est pas un, ils allouent une mémoire suffisamment alignée pour leur char. L'argument de l'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 serait peut-être préférable d'appeler les opérateurs globaux new et delete car cela contournerait l'intermédiaire.
new
réellement défini pour lancer. Ce n'est pas vrai. Il dépend du compilateur et du commutateur du compilateur. Voir par exemple les /GX[-] enable C++ EH (same as /EHsc)
commutateurs MSVC2019 . De plus, sur les systèmes embarqués, beaucoup choisissent de ne pas payer les taxes de performance pour les exceptions C ++. Donc la phrase commençant par "Mais vous perdez std :: bad_alloc ..." est discutable.