D'accord, je pense que nous sommes 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 être toutes sortes de choses différentes, et donc exécuter une inconditionnelle delete[]
dessus 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 le sait? Je veux dire, du point de vue du langage / compilateur, il n'a aucune idée si oui ou non arr
est un pointeur de tableau par rapport à un pointeur vers un seul int. Heck, il ne sait même pas s'il a arr
été créé dynamiquement. Pourtant, si je fais ce qui suit à la place,
int main()
{
int* num = new int(1);
deleteForMe(num);
return 0;
}
Le système d'exploitation est suffisamment intelligent pour ne supprimer qu'un seul int et ne pas se lancer dans un certain type de `` folie meurtrière '' en supprimant le reste de la mémoire au-delà de ce point (comparez cela avec strlen
et une \0
chaîne non terminée - il continuera jusqu'à ce qu'il atteint 0).
Alors, à qui revient-il de se souvenir de ces choses? Le système d'exploitation conserve-t-il un type d'enregistrement en arrière-plan? (Je veux dire, je me rends compte que j'ai commencé ce post en disant que ce qui se passe n'est pas défini, mais le fait est que le scénario de la `` folie meurtrière '' ne se produit pas, donc dans le monde pratique quelqu'un se souvient.)
Réponses:
Le compilateur ne sait pas que c'est un tableau, il fait confiance au programmeur. La suppression d'un pointeur vers un seul
int
avecdelete []
entraînerait un comportement indéfini. Votre deuxièmemain()
exemple est dangereux, même s'il ne plante pas immédiatement.Le compilateur doit garder une trace du nombre d'objets à supprimer d'une manière ou d'une autre. Il peut le faire en surallouant suffisamment pour stocker la taille du tableau. Pour plus de détails, consultez la Super FAQ C ++ .
la source
Une question que les réponses données jusqu'à présent ne semblent pas aborder: si les bibliothèques d'exécution (pas le système d'exploitation, vraiment) peuvent garder une trace du nombre de choses dans le tableau, alors pourquoi avons-nous besoin de la
delete[]
syntaxe? Pourquoi un seuldelete
formulaire ne peut-il pas être utilisé pour gérer toutes les suppressions?La réponse à cela remonte aux racines de C ++ en tant que langage compatible C (ce qu'il ne cherche plus vraiment à être.) La philosophie de Stroustrup était que le programmeur ne devrait pas avoir à payer pour des 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'objets pour chaque bloc de mémoire alloué.
Autrement dit, si votre code fait simplement
alors l'espace mémoire alloué
foo
ne devrait pas inclure de surcharge supplémentaire qui serait nécessaire pour prendre en charge les tableaux deFoo
.Étant donné que seules les allocations de tableau sont configurées pour transporter les informations de taille de tableau supplémentaires, vous devez alors indiquer aux bibliothèques d'exécution de rechercher ces informations lorsque vous supprimez les objets. C'est pourquoi nous devons utiliser
au lieu de juste
si bar est un pointeur vers un tableau.
Pour la plupart d'entre nous (moi y compris), cette inquiétude à propos de quelques octets supplémentaires de mémoire semble étrange ces jours-ci. Mais il existe encore des situations où la sauvegarde de quelques octets (à partir de ce qui pourrait être un nombre très élevé de blocs de mémoire) peut être importante.
la source
Oui, le système d'exploitation garde certaines choses en «arrière-plan». Par exemple, si vous exécutez
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 renvoyé pointe vers 1004, avec des emplacements 1000- 1003 stockant la taille de l'allocation). Ensuite, lorsque delete est appelé, il peut regarder 4 octets avant que le pointeur ne lui soit passé 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.
la source
for(int i = 0; i < *(arrayPointer - 1); i++){ }
Ceci est très similaire à cette question et contient de nombreux détails que vous recherchez.
Mais il suffit de dire que ce n'est pas le travail du système d'exploitation de suivre quoi que ce soit. Ce sont en fait les bibliothèques d'exécution ou le gestionnaire de mémoire sous-jacent qui suivront la taille du tableau. Cela se fait généralement en allouant de la mémoire supplémentaire à l'avance 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
la source
size_t size = *(reinterpret_cast<size_t *>(pArray) - 1)
placedelete
oudelete[]
libéreraient probablement tous les deux la mémoire allouée (mémoire pointée), mais la grande différence est quedelete
sur un tableau n'appelle pas le destructeur de chaque élément du tableau.Quoi qu'il en soit, le mixage
new/new[]
etdelete/delete[]
c'est probablement UB.la source
Il ne sait pas que c'est un tableau, c'est pourquoi vous devez fournir
delete[]
au lieu d'un anciendelete
.la source
J'avais une question similaire à cela. En C, vous allouez de la mémoire avec malloc () (ou une autre fonction similaire) et vous la supprimez 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 en C vous pouvez simplement remettre le pointeur sur free, mais en C ++, vous devez lui dire s'il s'agit d'un tableau ou d'une seule variable?
La réponse, j'ai appris, a à voir avec les destructeurs de classe.
Si vous allouez une instance d'une classe MyClass ...
Et supprimez-le avec delete, vous ne pouvez obtenir le destructeur que 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.
CECI 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 sur new [] et delete [] sur new n'est pas défini - il peut ne pas fonctionner de la même manière sur tous les compilateurs / systèmes.
la source
C'est au runtime 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 pour la taille du tableau.
Cependant, le runtime n'est pas assez intelligent pour détecter s'il s'agit ou non d'un tableau ou d'un pointeur, vous devez l'informer, et si vous vous trompez, soit vous ne supprimez pas correctement (par exemple, ptr au lieu de tableau), soit vous finissez par prendre une valeur sans rapport avec la taille et causer des dommages importants.
la source
L'UNE DES approches pour les compilateurs est d'allouer un peu plus de mémoire et de stocker le nombre d'éléments dans l'élément head.
Exemple comment cela pourrait être fait: ici
le compilateur allouera sizeof (int) * 5 octets.
Stockera
4
dans les premierssizeof(int)
octetsEt mettre
i
Alors
i
tableau de 4 éléments, pas 5.Et
sera traité de la manière suivante
la source
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 seul objet est donné à
delete[]
, alors UB en résultera, ce qui signifie que tout peut arriver, y compris une panne du système ou rien du tout.C ++ demande au programmeur de choisir la bonne version de l'opérateur de suppression en fonction du sujet de la désallocation: tableau ou objet unique.
Si le compilateur pouvait déterminer automatiquement si un pointeur passé à l'opérateur de suppression était un tableau de pointeurs, alors il n'y aurait qu'un seul opérateur de suppression en C ++, ce qui suffirait dans les deux cas.
la source
Convenez que le compilateur ne sait pas s'il s'agit d'un tableau ou non. Cela dépend du programmeur.
Le compilateur garde parfois une trace du nombre d'objets à supprimer en surallouant suffisamment pour stocker la taille du tableau, mais pas toujours nécessaire.
Pour une spécification complète lorsqu'un stockage supplémentaire est alloué, veuillez vous référer à C ++ ABI (comment les compilateurs sont implémentés): Itanium C ++ ABI: Array Operator new Cookies
la source
Vous ne pouvez pas utiliser delete pour un tableau et vous ne pouvez pas utiliser delete [] pour un non-tableau.
la source
«comportement indéfini» signifie simplement que la spécification du langage ne donne aucune garantie quant à ce qui va se passer. Cela ne signifie pas nécessairement que quelque chose de mauvais va se passer.
Il y a généralement deux couches ici. Le gestionnaire de mémoire sous-jacent et l'implémentation C ++.
En général, le gestionnaire de mémoire se souviendra (entre autres) de la taille du bloc de mémoire qui a été alloué. Cela peut être plus grand que le bloc demandé par l'implémentation C ++. En règle générale, le gestionnaire de mémoire stockera ses métadonnées avant le bloc de mémoire alloué.
L'implémentation C ++ ne se souviendra généralement de la taille du tableau que si elle a besoin de le faire pour ses propres besoins, généralement parce que le type a un destructeur non trival.
Donc, pour les types avec un destructeur trivial, l'implémentation de "delete" et "delete []" est généralement la même. L'implémentation C ++ transmet simplement le pointeur au gestionnaire de mémoire sous-jacent. Quelque chose comme
D'un autre côté, pour les types avec un destructeur non trivial, "delete" et "delete []" sont susceptibles d'être différents. "supprimer" serait quelque chose comme (où T est le type vers lequel le pointeur pointe)
Alors que "supprimer []" serait quelque chose comme.
la source
Parcourez un tableau d'objets et appelez destructor pour chacun d'entre eux. J'ai créé ce code simple qui surcharge les expressions new [] et delete [] et fournit une fonction de modèle pour désallouer la mémoire et appeler le destructeur pour chaque objet si nécessaire:
la source
définissez simplement un destructeur dans une classe et exécutez votre code avec les deux syntaxes
selon la sortie, vous pouvez trouver les solutions
la source
La réponse:
int * pArray = new int [5];
taille int = * (pArray-1);
La publication ci-dessus n'est pas correcte et produit une valeur non valide. Le "-1" compte les éléments Sur le système d'exploitation Windows 64 bits, la taille de tampon correcte réside dans l'adresse Ptr - 4 octets
la source