D’accord, je pense que nous sums tous d’accord pour dire que ce qui se passe avec le code suivant n’est pas défini, en fonction de ce qui est passé,
void deleteForMe(int* pointer) { delete[] pointer; }
Le pointeur peut avoir toutes sortes de choses différentes, et effectuer une delete[]
inconditionnelle delete[]
sur celui-ci n’est pas défini. Cependant, supposons que nous passons effectivement un pointeur de tableau,
int main() { int* arr = new int[5]; deleteForMe(arr); return 0; }
Ma question est, dans ce cas, où le pointeur est un tableau, qui est-ce qui le sait? Je veux dire, du sharepoint vue du langage / du compilateur, il n’a aucune idée si oui ou non arr
est un pointeur de tableau contre un pointeur vers un seul int. Heck, il ne sait même pas si arr
été créé dynamicment. Pourtant, si je fais ce qui suit,
int main() { int* num = new int(1); deleteForMe(num); return 0; }
Le système d’exploitation est assez intelligent pour ne supprimer qu’un seul int et ne pas se lancer dans une sorte de “spree de meurtre” en supprimant le rest de la mémoire au-delà de ce point (contraste avec strlen
et une chaîne non \0
-terminée. aller jusqu’à ce qu’il frappe 0).
Alors, à qui doit-on se souvenir de ces choses? Le système d’exploitation conserve-t-il un certain type de dossier en arrière-plan? (Je veux dire, je me rends compte que j’ai commencé ce post en disant que ce qui se passe est indéfini, mais le fait est que le scénario de la «tuerie» ne se produit pas, donc dans le monde pratique
Le compilateur ne sait pas que c’est un tableau, il fait confiance au programmeur. La suppression d’un pointeur sur un seul int
avec delete []
entraînerait un comportement indéfini. Votre deuxième exemple main()
n’est pas sûr, même s’il ne plante pas immédiatement.
Le compilateur doit garder une trace du nombre d’objects à supprimer d’une manière ou d’une autre. Cela peut se faire en sur-allouant suffisamment pour stocker la taille du tableau. Pour plus de détails, consultez la FAQ C ++ Super .
Une question que les réponses données jusqu’à présent ne semblent pas résoudre: si les bibliothèques d’exécution (et non le système d’exploitation, vraiment) peuvent garder une trace du nombre d’éléments dans le tableau, alors pourquoi avons-nous besoin de la syntaxe delete[]
? Pourquoi un seul formulaire de delete
ne peut-il pas être utilisé pour gérer toutes les suppressions?
La réponse à cette question remonte aux racines de C ++ en tant que langage compatible avec le langage C (dont il ne s’efforce plus d’être.) La philosophie de Stroustrup était que le programmeur ne devrait pas avoir à payer pour les fonctionnalités qu’il n’utilise pas. S’ils n’utilisent pas de tableaux, ils ne devraient pas avoir à supporter le coût des tableaux d’objects pour chaque bloc de mémoire alloué.
C’est-à-dire, si votre code le fait simplement
Foo* foo = new Foo;
alors l’espace mémoire alloué à foo
ne doit pas inclure de surcharge supplémentaire nécessaire pour prendre en charge les tableaux de Foo
.
Étant donné que seules les allocations de tableau sont configurées pour porter les informations de taille de tableau supplémentaires, vous devez ensuite demander aux bibliothèques d’exécution de rechercher ces informations lorsque vous supprimez les objects. C’est pourquoi nous devons utiliser
delete[] bar;
au lieu de juste
delete bar;
si la barre est un pointeur sur un tableau.
Pour la plupart d’entre nous (y compris moi-même), ce manque de mémoire de quelques octets supplémentaires semble étrange ces jours-ci. Mais il y a encore des situations où économiser quelques octets (à partir de ce qui pourrait être un très grand nombre de blocs de mémoire) peut être important.
Oui, le système d’exploitation garde certaines choses en arrière-plan. Par exemple, si vous exécutez
int* num = new int[5];
le système d’exploitation peut allouer 4 octets supplémentaires, stocker la taille de l’allocation dans les 4 premiers octets de la mémoire allouée et renvoyer un pointeur de décalage (c’est-à-dire qu’il alloue des espaces mémoire de 1000 à 1024 mais le pointeur revient à 1004). 1003 stocker la taille de l’allocation). Ensuite, lorsque la suppression est appelée, elle peut afficher 4 octets avant que le pointeur ne lui soit transmis pour trouver la taille de l’allocation.
Je suis sûr qu’il existe d’autres moyens de suivre la taille d’une allocation, mais c’est une option.
Ceci est très similaire à cette question et il a beaucoup de détails que vous recherchez.
Mais il suffit de dire que ce n’est pas la tâche de l’OS de faire le suivi de tout cela. C’est en fait les bibliothèques d’exécution ou le gestionnaire de mémoire sous-jacent qui vont suivre la taille du tableau. Cela se fait généralement en allouant de la mémoire supplémentaire et en stockant la taille du tableau à cet emplacement (la plupart utilisent un nœud principal).
Ceci est visible sur certaines implémentations en exécutant le code suivant
int* pArray = new int[5]; int size = *(pArray-1);
delete
ou delete[]
libérerait probablement à la fois la mémoire allouée (pointage mémoire), mais la grande différence est que la delete
sur un tableau n’appelle pas le destructeur de chaque élément du tableau.
Quoi qu’il en soit, mélanger new/new[]
et delete/delete[]
est probablement UB.
Il ne sait pas que c’est un tableau, c’est pourquoi vous devez fournir delete[]
au lieu de l’ancienne delete
classique.
J’ai eu une question similaire à cela. En C, vous allouez de la mémoire avec malloc () (ou une autre fonction similaire) et supprimez-la avec free (). Il n’y a qu’un seul malloc () qui alloue simplement un certain nombre d’octets. Il n’y a qu’un seul free (), qui prend simplement un pointeur comme paramètre.
Alors, pourquoi est-il possible de transférer le pointeur sur C libre, mais en C ++, vous devez lui indiquer si c’est un tableau ou une seule variable?
La réponse, j’ai appris, concerne les destructeurs de classe.
Si vous allouez une instance d’une classe MyClass …
classes = new MyClass[3];
Et supprimez-le avec delete, vous pouvez seulement obtenir le destructeur pour la première instance de MyClass appelée. Si vous utilisez delete [], vous pouvez être assuré que le destructeur sera appelé pour toutes les instances du tableau.
C’est la différence importante. Si vous travaillez simplement avec des types standard (par exemple, int), vous ne verrez pas vraiment ce problème. De plus, vous devez vous rappeler que le comportement d’utilisation de delete on new [] et delete [] sur new n’est pas défini – il se peut qu’il ne fonctionne pas de la même manière sur chaque compilateur / système.
C’est à l’exécution qui est responsable de l’allocation de mémoire, de la même manière que vous pouvez supprimer un tableau créé avec malloc en standard C en utilisant free. Je pense que chaque compilateur l’implémente différemment. Une méthode courante consiste à allouer une cellule supplémentaire à la taille du tableau.
Cependant, le runtime n’est pas assez intelligent pour détecter si oui ou non il s’agit d’un tableau ou d’un pointeur, vous devez l’informer et si vous vous trompez, vous ne supprimez pas correctement (par exemple, ptr au lieu du tableau) vous finissez par prendre une valeur sans rapport avec la taille et causez des dégâts importants.
L’une des approches pour les compilateurs est d’allouer un peu plus de mémoire et de stocker des éléments dans l’élément principal.
Exemple comment cela pourrait être fait: ici
int* i = new int[4];
le compilateur allouera sizeof (int) * 5 octets.
int *temp = malloc(sizeof(int)*5)
Va stocker 4
en première sizeof(int)
octets
*temp = 4;
et mis i
i = temp + 1;
Donc, i
pointe vers un tableau de 4 éléments, pas 5.
Et
delete[] i;
sera traité de la manière suivante
int *temp = i - 1; int numbers_of_element = *temp; // = 4 ... call destructor for numbers_of_element elements if needed ... that are stored in temp + 1, temp + 2, ... temp + 4 free (temp)
Convenez que le compilateur ne sait pas si c’est un tableau ou non. C’est au programmeur.
Le compilateur garde parfois la trace du nombre d’objects à supprimer en allouant suffisamment pour stocker la taille du tableau, mais pas toujours nécessaire.
Pour une spécification complète lors de l’allocation d’un espace de stockage supplémentaire, reportez-vous à C ++ ABI (implémentation des compilateurs): Itanium C ++ ABI: Opérateur de tableau Nouveaux cookies
Sémantiquement, les deux versions de l’opérateur de suppression en C ++ peuvent “manger” n’importe quel pointeur; cependant, si un pointeur vers un object unique est donné pour delete[]
, alors UB se traduira, ce qui signifie que tout peut arriver, y compris un blocage du système ou rien du tout.
C ++ exige que le programmeur choisisse la version correcte de l’opérateur delete en fonction de l’object de la dissortingbution: tableau ou object unique.
Si le compilateur pouvait automatiquement déterminer si un pointeur transmis à l’opérateur delete était un tableau de pointeurs, il n’y aurait qu’un seul opérateur de suppression en C ++, ce qui serait suffisant pour les deux cas.
Vous ne pouvez pas utiliser delete pour un tableau et vous ne pouvez pas utiliser delete [] pour un non-tableau.
Hé bien, cela dépend de ce que vous allouez avec new [] expression lorsque vous allouez un tableau de types ou de classe / structure et que vous ne fournissez pas votre constructeur et votre destructeur, l’opérateur le traitera comme une taille “sizeof (object) * numObjects “plutôt que tableau d’objects dans ce cas, le nombre d’objects alloués ne sera stocké nulle part, cependant si vous allouez un tableau d’objects et fournissez un constructeur et un destructeur dans votre object plutôt qu’un changement de comportement, la nouvelle expression allouera 4 octets de plus objects dans les 4 premiers octets afin que le destructeur pour chacun d’eux puisse être appelé et donc new [] expression retournera le pointeur décalé de 4 octets vers l’avant, que lorsque la mémoire est renvoyée, l’expression delete [] appellera d’abord un modèle de fonction, itérera à travers tableau d’objects et destructeur d’appel pour chacun d’eux. J’ai créé ce code simple qui surcharge les nouvelles expressions [] et delete [] et fournit une fonction de modèle pour désallouer la mémoire et appeler un destructeur pour chaque object si nécessaire:
// overloaded new expression void* operator new[]( size_t size ) { // allocate 4 bytes more see comment below int* ptr = (int*)malloc( size + 4 ); // set value stored at address to 0 // and shift pointer by 4 bytes to avoid situation that // might arise where two memory blocks // are adjacent and non-zero *ptr = 0; ++ptr; return ptr; } ////////////////////////////////////////// // overloaded delete expression void static operator delete[]( void* ptr ) { // decrement value of pointer to get the // "Real Pointer Value" int* realPtr = (int*)ptr; --realPtr; free( realPtr ); } ////////////////////////////////////////// // Template used to call destructor if needed // and call appropriate delete template void Deallocate( T* ptr ) { int* instanceCount = (int*)ptr; --instanceCount; if(*instanceCount > 0) // if larger than 0 array is being deleted { // call destructor for each object for(int i = 0; i < *instanceCount; i++) { ptr[i].~T(); } // call delete passing instance count witch points // to begin of array memory ::operator delete[]( instanceCount ); } else { // single instance deleted call destructor // and delete passing ptr ptr->~T(); ::operator delete[]( ptr ); } } // Replace calls to new and delete #define MyNew ::new #define MyDelete(ptr) Deallocate(ptr) // structure with constructor/ destructor struct StructureOne { StructureOne(): someInt(0) {} ~StructureOne() { someInt = 0; } int someInt; }; ////////////////////////////// // structure without constructor/ destructor struct StructureTwo { int someInt; }; ////////////////////////////// void main(void) { const unsigned int numElements = 30; StructureOne* structOne = nullptr; StructureTwo* structTwo = nullptr; int* basicType = nullptr; size_t ArraySize = 0; /**********************************************************************/ // basic type array // place break point here and in new expression // check size and compare it with size passed // in to new expression size will be the same ArraySize = sizeof( int ) * numElements; // this will be treated as size rather than object array as there is no // constructor and destructor. value assigned to basicType pointer // will be the same as value of "++ptr" in new expression basicType = MyNew int[numElements]; // Place break point in template function to see the behavior // destructors will not be called and it will be treated as // single instance of size equal to "sizeof( int ) * numElements" MyDelete( basicType ); /**********************************************************************/ // structure without constructor and destructor array // behavior will be the same as with basic type // place break point here and in new expression // check size and compare it with size passed // in to new expression size will be the same ArraySize = sizeof( StructureTwo ) * numElements; // this will be treated as size rather than object array as there is no // constructor and destructor value assigned to structTwo pointer // will be the same as value of "++ptr" in new expression structTwo = MyNew StructureTwo[numElements]; // Place break point in template function to see the behavior // destructors will not be called and it will be treated as // single instance of size equal to "sizeof( StructureTwo ) * numElements" MyDelete( structTwo ); /**********************************************************************/ // structure with constructor and destructor array // place break point check size and compare it with size passed in // new expression size in expression will be larger by 4 bytes ArraySize = sizeof( StructureOne ) * numElements; // value assigned to "structOne pointer" will be different // of "++ptr" in new expression "shifted by another 4 bytes" structOne = MyNew StructureOne[numElements]; // Place break point in template function to see the behavior // destructors will be called for each array object MyDelete( structOne ); } ///////////////////////////////////////////
il suffit de définir un destructeur à l’intérieur d’une classe et d’exécuter votre code avec les deux syntaxes
delete pointer delete [] pointer
selon la sortie, vous pouvez trouver les solutions
La réponse:
int * pArray = new int [5];
int size = * (pArray-1);
Publié ci-dessus n’est pas correct et produit une valeur non valide. Le “-1” compte les éléments Sur un système d’exploitation Windows 64 bits, la taille correcte du tampon réside dans Ptr – adresse de 4 octets