web-dev-qa-db-fra.com

Est-il sûr de réaffecter de la mémoire allouée à une nouvelle?

D'après ce qui est écrit ici , new alloue dans le magasin gratuit tandis que malloc utilise tas et les deux termes signifient souvent la même chose.

D'après ce qui est écrit ici , realloc peut déplacer le bloc de mémoire vers un nouvel emplacement. Si le stockage gratuit et le tas sont deux espaces mémoire différents, cela signifie-t-il alors un problème?

Plus précisément, j'aimerais savoir si son utilisation est sûre

int* data = new int[3];
// ...
int* mydata = (int*)realloc(data,6*sizeof(int));

Sinon, existe-t-il un autre moyen de realloc mémoire allouée avec new en toute sécurité? Je pourrais allouer une nouvelle zone et memcpy le contenu, mais d'après ce que je comprends, realloc peut utiliser la même zone si possible.

33
Jan Turoň

Vous ne pouvez que realloc ce qui a été alloué via malloc (ou famille, comme calloc).

En effet, les structures de données sous-jacentes qui gardent une trace des zones de mémoire libres et utilisées peuvent être très différentes.

C'est probablement mais en aucun cas garanti que C++ new et C malloc utilisent le même allocateur sous-jacent, auquel cas realloc pourrait fonctionner pour les deux. Mais formellement, c'est en UB-land. Et dans la pratique, c'est juste inutilement risqué.


C++ n'offre pas de fonctionnalité correspondant à realloc.

La plus proche est la réallocation automatique des (tampons internes des) conteneurs comme std::vector.

Les conteneurs C++ souffrent d'être conçus d'une manière qui exclut l'utilisation de realloc.


Au lieu du code présenté

int* data = new int[3];
//...
int* mydata = (int*)realloc(data,6*sizeof(int));

… Faites ceci:

vector<int> data( 3 );
//...
data.resize( 6 );

Cependant, si vous avez absolument besoin de l'efficacité générale de realloc, et si vous devez accepter new pour l'allocation d'origine, alors votre seul recours pour l'efficacité est d'utiliser des moyens spécifiques au compilateur, sachant que realloc est sûr avec ce compilateur.

Sinon, si vous avez absolument besoin de l'efficacité générale de realloc mais n'êtes pas obligé d'accepter new, alors vous pouvez utiliser malloc et realloc. L'utilisation de pointeurs intelligents vous permet alors d'obtenir la même sécurité qu'avec les conteneurs C++.

46

La seule restriction éventuellement pertinente que C++ ajoute à realloc est que les malloc/calloc/realloc de C++ ne doivent pas être implémentés en termes de ::operator new , et son free ne doit pas être implémenté en termes de ::operator delete (par C++ 14 [c.malloc] p3-4).

Cela signifie que la garantie que vous recherchez n'existe pas en C++. Cela signifie également, cependant, que vous pouvez implémenter ::operator new En termes de malloc. Et si vous faites cela, alors en théorie, le résultat de ::operator new Peut être passé à realloc.

En pratique, vous devez vous inquiéter de la possibilité que le résultat de new ne corresponde pas au résultat de ::operator new. Les compilateurs C++ peuvent par exemple combiner plusieurs expressions new pour utiliser un seul appel ::operator new. C'est quelque chose que les compilateurs ont déjà fait lorsque la norme ne le permettait pas, IIRC, et la norme le permet maintenant (selon C++ 14 [expr.new] p10). Cela signifie que même si vous suivez cette route, vous n'avez toujours pas la garantie que le passage de vos pointeurs new vers realloc a quelque chose de significatif, même si ce n'est plus un comportement indéfini.

16
user743382

En général, ne faites pas ça. Si vous utilisez des types définis par l'utilisateur avec initialisation non triviale , en cas de libération de copie de réallocation, le destructeur de vos objets a gagné pas appelé par realloc. La copie le constructeur ne sera pas appelé aussi, lors de la copie. Cela peut conduire à un comportement indéfini en raison d'une utilisation incorrecte de la durée de vie de l'objet (voir Norme C++ §3.8 Durée de vie de l'objet, [basic.life] ).

1 La durée de vie d'un objet est une propriété d'exécution de l'objet. Un objet est dit avoir une initialisation non triviale s'il est d'un type classe ou agrégat et que lui ou l'un de ses membres est initialisé par un constructeur autre qu'un constructeur par défaut trivial. [Remarque: l'initialisation par un constructeur de copie/déplacement trivial est une initialisation non triviale. —Fin note]

La durée de vie d'un objet de type T commence lorsque:

- un stockage avec l'alignement et la taille appropriés pour le type T est obtenu, et

- si l'objet a une initialisation non triviale, son initialisation est terminée.

La durée de vie d'un objet de type T se termine lorsque:

- si T est un type de classe avec un destructeur non trivial (12.4), l'appel du destructeur démarre, ou

- le stockage occupé par l'objet est réutilisé ou libéré.

Et plus tard (c'est moi qui souligne):

3 Les propriétés attribuées aux objets dans la présente Norme internationale s'appliquent à un objet donné niquement pendant sa durée de vie.

Donc, vous ne voulez vraiment pas utiliser un objet hors de sa durée de vie.

8
Paolo M

Ce n'est pas sûr et ce n'est pas élégant.

Il peut être possible de remplacer new/delete pour prendre en charge la réaffectation, mais vous pouvez également envisager d'utiliser les conteneurs.

5

Oui - si new a effectivement appelé malloc en premier lieu (par exemple, c'est ainsi que VC++ new fonctionne).

Non sinon. notez qu'une fois que vous décidez de réallouer la mémoire (parce que new appelé malloc), votre code est spécifique au compilateur et n'est plus portable entre les compilateurs.

(Je sais que cette réponse peut contrarier de nombreux développeurs, mais je réponds que cela dépend de faits réels, pas seulement de l'idiomatie).

4
David Haim

Ce n'est pas sûr. Tout d'abord, le pointeur que vous passez à realloc doit avoir été obtenu à partir de malloc ou realloc: http://en.cppreference.com/w/cpp/memory/c/realloc .

Deuxièmement, le résultat de new int [3] n'a pas besoin d'être le même que le résultat de la fonction d'allocation - un espace supplémentaire peut être alloué pour stocker le nombre d'éléments.

(Et pour des types plus complexes que int, realloc ne serait pas sûr car il n'appelle pas les constructeurs de copie ou de déplacement.)

4
Alan Stokes

En général, non.

Il y a un tas de choses qui doivent tenir pour le rendre sûr:

  1. La copie au niveau du bit et l'abandon de la source doivent être sécurisées.
  2. Le destructeur doit être trivial, ou vous devez détruire sur place les éléments que vous souhaitez désallouer.
  3. Soit le constructeur est trivial, soit vous devez construire sur place les nouveaux éléments.

Les types triviaux satisfont aux exigences ci-dessus.

En outre:

  1. Le new[]- la fonction doit transmettre la demande à malloc sans aucune modification, ni aucune comptabilité sur le côté. Vous pouvez forcer cela en remplaçant global new [] et en supprimant [], ou ceux des classes respectives.
  2. Le compilateur ne doit pas demander plus de mémoire pour sauvegarder le nombre d'éléments alloués, ou autre chose.
    Il n'y a aucun moyen de forcer cela, même si un compilateur ne doit pas enregistrer de telles informations si le type a un destructeur trivial en raison de Quality of Implementation.
4
Deduplicator

Vous pouvez peut-être (pas dans tous les cas), mais vous ne devriez pas. Si vous devez redimensionner votre tableau de données, vous devez utiliser std::vector au lieu.

Les détails sur la façon de l'utiliser sont listés dans un autre SO question .

3
nikaltipar