Comment supprimer [] "connaît" la taille du tableau d'opérandes?

250
Foo* set = new Foo[100];
// ...
delete [] set;

Vous ne passez pas les limites du tableau à delete[]. Mais où sont stockées ces informations? Est-ce standardisé?

VolkerK
la source
sourceforge.net/projects/fastmm est open source et remplace le gestionnaire de mémoire. Vous pouvez découvrir ici comment fonctionne la gestion de la mémoire et d'où proviennent les informations pour allouer et supprimer des mémoires.
1
Notez que FastMM est spécifique aux compilateurs Delphi / C ++ Builder uniquement, ce n'est pas un gestionnaire de mémoire à usage général pour C ++. Il n'est même pas écrit en C ++.
Remy Lebeau

Réponses:

181

Lorsque vous allouez de la mémoire sur le tas, votre allocateur gardera une trace de la quantité de mémoire que vous avez allouée. Ceci est généralement stocké dans un segment "tête" juste avant la mémoire qui vous est allouée. De cette façon, quand il est temps de libérer la mémoire, le désallocateur sait exactement la quantité de mémoire à libérer.

Peter Kühne
la source
4
Notez que cela s'applique uniquement aux allocations de tableaux en C ++. Toutes les autres allocations dépendent de la taille du type. Certaines bibliothèques stockent toutes les tailles d'allocation, généralement uniquement en mode débogage.
Zan Lynx
97
Il n'y a absolument aucune raison pour que ces informations ne soient pas disponibles pour le programmeur. Je peux passer un pointeur sur une fonction et la libérer, mais pour obtenir la taille moi-même dans cette même fonction, je dois passer un paramètre supplémentaire. Cela a-t-il un sens?
Mark Ruzon
26
@Mark, cela a un peu de sens, car en théorie, il libère l'allocateur pour toujours stocker la taille du bloc alloué (qui peut différer de la taille du bloc demandé ). Certaines conceptions d'allocateurs peuvent avoir besoin de ces informations à leurs propres fins ou ne pas être suffisamment sophistiquées pour utiliser les informations de type pour suivre la taille des allocations de tas non-tableau, etc. Forcer l'allocateur à stocker la taille demandée (de sorte que vous ne le feriez pas besoin de passer vous-même la taille de la baie) peut être un petit encombrement, mais cela peut avoir des répercussions sur les performances des conceptions d'allocateurs imaginables.
Doug McClean
33
Désolé, mais cette réponse manque le point. Ce que QuantumPete a décrit est essentiellement "Comment freesait combien de mémoire à désallouer". Oui, la taille du bloc de mémoire est stockée "quelque part" par malloc(normalement dans le bloc lui-même), c'est ainsi que l'on freesait. Cependant, new[]/ delete[]est une autre histoire. Ces derniers fonctionnent essentiellement sur malloc/ free. new[]stocke également le nombre d'éléments qu'il a créés dans le bloc de mémoire (indépendamment de malloc), afin que plus tard, il delete[]puisse récupérer et utiliser ce numéro pour appeler le nombre approprié de destructeurs.
AnT
26
C'est-à-dire que physiquement deux compteurs sont stockés dans le bloc: la taille du bloc (par malloc) et le nombre d'éléments (par new[]). Notez que le premier ne peut pas être utilisé pour calculer le second, car en général, la taille du bloc de mémoire peut être plus grande que ce qui est vraiment nécessaire pour le tableau de la taille demandée. Notez également que le compteur d'éléments de tableau n'est nécessaire que pour les types avec destructeur non trivial. Pour les types avec destructeur trivial, le compteur n'est pas stocké par new[]et, bien sûr, n'est pas récupéré par delete[].
AnT
23

L'une des approches pour les compilateurs est d'allouer un peu plus de mémoire et de stocker un nombre d'éléments dans un élément head.

Exemple comment cela pourrait être fait:

Ici

int* i = new int[4];

le compilateur allouera des sizeof(int)*5octets.

int *temp = malloc(sizeof(int)*5)

Stockera "4" dans les premiers sizeof(int)octets

*temp = 4;

Et mettre i

i = temp + 1;

Il en iva de même pour un tableau de 4 éléments, pas 5.

Et suppression

delete[] i;

seront traitées de la manière suivante:

int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements
... that are stored in temp + 1, temp + 2, ... temp + 4 if needed
free (temp)
Avt
la source
9

Les informations ne sont pas standardisées. Cependant, dans les plates-formes sur lesquelles j'ai travaillé, ces informations sont stockées en mémoire juste avant le premier élément. Par conséquent, vous pourriez théoriquement y accéder et l'inspecter, mais cela n'en vaut pas la peine.

C'est aussi pourquoi vous devez utiliser delete [] lorsque vous avez alloué de la mémoire avec new [], car la version de tableau de delete sait que (et où) elle doit chercher à libérer la bonne quantité de mémoire - et appeler le nombre approprié de destructeurs pour les objets.

Daemin
la source
5

Fondamentalement, il est organisé en mémoire comme:

[info] [mem que vous avez demandé ...]

Où info est la structure utilisée par votre compilateur pour stocker la quantité de mémoire allouée, et quoi d'autre.

Cela dépend cependant de l'implémentation.

Francisco Soto
la source
4

Ce n'est pas quelque chose qui est dans la spécification - cela dépend de l'implémentation.

jeffm
la source
3

Il est défini dans la norme C ++ pour être spécifique au compilateur. Ce qui signifie la magie du compilateur. Il peut rompre avec des restrictions d'alignement non triviales sur au moins une plate-forme majeure.

Vous pouvez penser aux implémentations possibles en réalisant que cela delete[]n'est défini que pour les pointeurs retournés par new[], qui peuvent ne pas être le même pointeur que retourné par operator new[]. Une implémentation dans la nature consiste à stocker le nombre de tableaux dans le premier entier retourné par operator new[], et à new[]renvoyer un pointeur décalé au-delà. (C'est pourquoi les alignements non triviaux peuvent se rompre new[].)

Gardez à l'esprit que operator new[]/operator delete[]! = new[]/delete[].

De plus, cela est orthogonal à la façon dont C connaît la taille de la mémoire allouée par malloc.

MSN
la source
2

Parce que le tableau à «supprimer» aurait dû être créé avec une seule utilisation de l'opérateur «nouveau». La «nouvelle» opération aurait dû mettre ces informations sur le tas. Sinon, comment les utilisations supplémentaires de new sauraient-elles où se termine le tas?

Joel Coehoorn
la source
0

Ce n'est pas standardisé. Dans le runtime de Microsoft, le nouvel opérateur utilise malloc () et l'opérateur de suppression utilise free (). Ainsi, dans ce paramètre, votre question est équivalente à la suivante: comment free () connaît-il la taille du bloc?

Il y a une comptabilité en cours dans les coulisses, c'est-à-dire dans le runtime C.

André
la source
5
Pas vrai. Avant d'appeler gratuitement, la suppression [] doit d'abord appeler les destructeurs. Pour cela, la taille totale des allocations ne suffit pas. En fait new [] et delete [] fonctionnent différemment pour les types simples et détruits dans VC ++.
Suma
0

C'est un problème plus intéressant que vous ne le pensez au début. Cette réponse concerne une implémentation possible.

Premièrement, alors qu'à un certain niveau votre système doit savoir comment «libérer» le bloc de mémoire, le malloc / free sous-jacent (que new / delete / new [] / delete [] appellent généralement) ne se souvient pas toujours exactement de la quantité de mémoire vous demandez, il peut être arrondi (par exemple, une fois que vous êtes au-dessus de 4K, il est souvent arrondi au bloc de taille 4K suivant).

Par conséquent, même si pourrait obtenir la taille du bloc de mémoire, cela ne nous dit pas combien de valeurs sont dans la nouvelle mémoire [], car elle peut être plus petite. Par conséquent, nous devons stocker un entier supplémentaire nous indiquant le nombre de valeurs.

SAUF, si le type en cours de construction n'a pas de destructeur, alors delete [] n'a rien d'autre à faire que de libérer le bloc mémoire, et donc n'a rien à stocker!

Chris Jefferson
la source