Comment free sait-il combien à libérer?

385

En programmation C, vous pouvez passer n'importe quel type de pointeur que vous aimez comme argument à libérer, comment connaît-il la taille de la mémoire allouée à libérer? Chaque fois que je passe un pointeur vers une fonction, je dois également passer la taille (c'est-à-dire qu'un tableau de 10 éléments doit recevoir 10 comme paramètre pour connaître la taille du tableau), mais je n'ai pas à transmettre la taille à la fonction libre. Pourquoi pas, et puis-je utiliser cette même technique dans mes propres fonctions pour m'éviter d'avoir à contourner la variable supplémentaire de la longueur du tableau?

Joshua Cheek
la source
Une question similaire: stackoverflow.com/questions/851958/… (même si je dirais que ce n'est pas tout à fait en double)
John Carter
Le système de jumelage est une autre façon de le faire qui peut être basée sur le pointeur, sans frais généraux dans chaque bloc.
EvilTeach
Cet article l'explique bien: stackoverflow.com/questions/1957099/…
Zeeshan Mahmood

Réponses:

349

Lorsque vous appelez malloc(), vous spécifiez la quantité de mémoire à allouer. La quantité de mémoire réellement utilisée est légèrement supérieure à cela et comprend des informations supplémentaires qui enregistrent (au moins) la taille du bloc. Vous ne pouvez pas (de manière fiable) accéder à ces autres informations - et vous ne devriez pas non plus :-).

Lorsque vous appelez free(), il regarde simplement les informations supplémentaires pour savoir quelle est la taille du bloc.

Gary McGill
la source
44
Pour info, par exemple BSD doit malloc_size()accéder de manière fiable à la taille de bloc à partir d'un malloc()pointeur ed. Mais il n'y a aucun moyen fiable et portable.
laalto
50
Je pense qu'il est important de dire que ce bloc d'informations supplémentaires est situé avant le pointeur renvoyé.
Georg Schölly
39
@gs Eh bien, cela dépend de l'implémentation. Mais oui, c'est là que ça se passe habituellement.
Falaina
31
Pouvez-vous imaginer l'horreur si free()le programmeur devait indiquer avec précision la taille du malloc()bloc? Les fuites de mémoire sont déjà assez mauvaises.
MusiGenesis
35
Pourquoi ces informations sont-elles disponibles pour malloc()et free(), mais vous devez stocker la taille d'un tableau? Pourquoi ne permettraient-ils pas de faire quelque chose comme blockSize(ptr)s'ils stockaient quand même les informations?
corsiKa
144

La plupart des implémentations des fonctions d'allocation de mémoire C stockent des informations comptables pour chaque bloc, soit en ligne, soit séparément.

Une façon typique (en ligne) consiste à allouer à la fois un en-tête et la mémoire que vous avez demandée, remplie à une taille minimale. Ainsi, par exemple, si vous avez demandé 20 octets, le système peut allouer un bloc de 48 octets:

  • En-tête de 16 octets contenant la taille, un marqueur spécial, une somme de contrôle, des pointeurs vers le bloc suivant / précédent, etc.
  • Zone de données de 32 octets (vos 20 octets remplis à un multiple de 16).

L'adresse qui vous est alors donnée est l'adresse de la zone de données. Ensuite, lorsque vous libérez le bloc, freeprenez simplement l'adresse que vous lui donnez et, en supposant que vous n'avez pas bourré cette adresse ou la mémoire qui l'entoure, vérifiez les informations comptables immédiatement avant. Graphiquement, ce serait dans le sens de:

 ____ The allocated block ____
/                             \
+--------+--------------------+
| Header | Your data area ... |
+--------+--------------------+
          ^
          |
          +-- The address you are given

Gardez à l'esprit que la taille de l'en-tête et le remplissage sont totalement définis par l'implémentation (en fait, le tout est défini par l'implémentation (a) mais l'option de comptabilité en ligne est courante).

Les sommes de contrôle et les marqueurs spéciaux qui existent dans les informations comptables sont souvent à l'origine d'erreurs telles que «Arène mémoire corrompue» ou «Double libre» si vous les écrasez ou les libérez deux fois.

Le remplissage (pour rendre l'allocation plus efficace) est la raison pour laquelle vous pouvez parfois écrire un peu au-delà de la fin de votre espace demandé sans causer de problèmes (encore, ne faites pas cela, c'est un comportement indéfini et, juste parce que cela fonctionne parfois, ne fonctionne pas) Je veux dire que c'est correct de le faire).


(a) J'ai écrit des implémentations de mallocdans les systèmes embarqués où vous avez obtenu 128 octets, peu importe ce que vous avez demandé (c'était la taille de la plus grande structure du système), en supposant que vous avez demandé 128 octets ou moins (les demandes de plus seraient être rencontré avec une valeur de retour NULL). Un masque de bits très simple (c'est-à-dire non en ligne) a été utilisé pour décider si un bloc de 128 octets était alloué ou non.

D'autres que j'ai développés avaient différents pools pour les blocs de 16 octets, les blocs de 64 octets, les blocs de 256 octets et les blocs de 1 Ko, utilisant à nouveau un masque de bits pour décider quels blocs étaient utilisés ou disponibles.

Ces deux options ont réussi à réduire les frais généraux de l'information comptable et d'augmenter la vitesse mallocet free(pas besoin de fusionner des blocs adjacents lors de la libération), particulièrement important dans l'environnement que nous travaillions dans.

paxdiablo
la source
@paxdiablo Est-ce à dire que malloc n'alloue pas de blocs de mémoire contigus?
user10678
2
@ user10678, la seule vraie exigence mallocest qu'il vous donne, pour le cas réussi, un bloc de mémoire au moins aussi grand que ce que vous avez demandé. Les blocs individuels sont contigus en termes de la façon dont vous accédez aux éléments qu'ils contiennent, mais il n'est pas nécessaire que les arènes d'où proviennent les blocs soient contiguës.
paxdiablo
Question connexe: pourquoi n'y a-t-il pas de variation de malloc / free, où vous spécifiez la taille lors de la libération et qu'il n'est donc pas nécessaire de stocker la taille?
user253751
@ user253751, parce qu'encore une autre chose dont vous avez besoin pour garder une trace, au-delà du pointeur lui-même. Il est à la fois inutile et dangereux: void *x = malloc(200); free(x, 500);on ne va pas bien finir :-) Dans tous les cas, l' efficacité, la réelle taille de la mémoire tampon peut être plus grande (vous ne pouvez pas compter sur ce sujet ).
paxdiablo
@paxdiablo Il évite également de gaspiller de la mémoire pour conserver la taille.
user253751
47

De la comp.lang.cliste FAQ: comment free sait-il combien d'octets libérer?

L'implémentation malloc / free se souvient de la taille de chaque bloc lorsqu'elle est allouée, il n'est donc pas nécessaire de lui rappeler la taille lors de la libération. (En règle générale, la taille est stockée à côté du bloc alloué, c'est pourquoi les choses se cassent généralement mal si les limites du bloc alloué sont même légèrement dépassées)

jdehaan
la source
2
Ceci est une non réponse. La question est exactement la suivante: pourquoi free peut-il rechercher de manière fiable la taille du bloc, mais pourtant il n'y a pas de fonction disponible pour le programmeur qui fait cela?
Bananach
Il s'agit en effet d'un détail d'implémentation pour l'API malloc et il n'y a pas d'API pour récupérer ces informations de manière standard (à ma connaissance). Le "système" l'enregistre et l'utilise sur free. Peut-être que la réponse ne vous satisfait pas, mais je ne pense pas que vous en obtiendrez une avec des informations plus applicables :-)
jdehaan
6

Cette réponse est déplacée à partir de Comment free () sait-il combien de mémoire désallouer? où j'ai été brutalement empêché de répondre par une question apparemment en double. Cette réponse devrait alors être pertinente pour ce double:


Dans le cas de malloc, l'allocateur de tas stocke un mappage du pointeur renvoyé d'origine, avec les détails pertinents nécessaires pour freeing la mémoire plus tard. Cela implique généralement le stockage de la taille de la région de mémoire sous quelque forme que ce soit pertinente pour l'allocateur utilisé, par exemple la taille brute, ou un nœud dans un arbre binaire utilisé pour suivre les allocations, ou un décompte des "unités" de mémoire utilisées.

freen'échouera pas si vous "renommez" le pointeur ou si vous le dupliquez de quelque manière que ce soit. Elle n'est cependant pas comptée en référence, et seule la première freesera correcte. Les frees supplémentaires sont des erreurs "double free".

Tentative de freen'importe quel pointeur avec une valeur différente de celles renvoyées par les mallocs précédents et non libérée est une erreur. Il n'est pas possible de libérer partiellement les régions de mémoire renvoyées par malloc.

Matt Joiner
la source
J'ai changé la valeur d'un pointeur renvoyé par un appel malloc. Et je l'ai libéré sans erreur. Pourquoi? Voir ici: stackoverflow.com/questions/42618390/…
smwikipedia
4

Sur une note connexe, la bibliothèque GLib a des fonctions d'allocation de mémoire qui n'enregistrent pas la taille implicite - et vous passez simplement le paramètre de taille à free. Cela peut éliminer une partie des frais généraux.

EFraim
la source
3

malloc()et free()dépendent du système / compilateur, il est donc difficile de donner une réponse spécifique.

Plus d'informations sur cette autre question .

LiraNuna
la source
2
Ils dépendent vraiment de la bibliothèque (généralement la bibliothèque C, qui est généralement très étroitement liée au système d'exploitation). Pour le compilateur, ce ne sont que des fonctions.
Donal Fellows
2

Le gestionnaire de tas a stocké la quantité de mémoire appartenant au bloc alloué quelque part lorsque vous avez appelé malloc.

Je n'en ai jamais implémenté un moi-même, mais je suppose que la mémoire juste en face du bloc alloué peut contenir les métadonnées.

Timbo
la source
3
C'est une implémentation possible, mais on pourrait concevoir un système où toute la mémoire est suivie dans une seule table dans une page totalement différente, pas nécessairement n'importe où près du pool de mémoire à partir duquel elle est allouée.
éphémère
2

La technique originale était d'allouer un bloc légèrement plus grand et de stocker la taille au début, puis de donner à l'application le reste du blog. L'espace supplémentaire contient une taille et peut-être des liens pour enfiler les blocs libres pour une réutilisation.

Cependant, il existe certains problèmes avec ces astuces, comme un mauvais comportement de gestion du cache et de la mémoire. Utiliser la mémoire directement dans le bloc tend à paginer inutilement les choses et crée également des pages sales qui compliquent le partage et la copie sur écriture.

Une technique plus avancée consiste donc à conserver un répertoire séparé. Des approches exotiques ont également été développées où les zones de mémoire utilisent les mêmes tailles de puissance de deux.

En général, la réponse est: une structure de données distincte est allouée pour conserver l'état.

DigitalRoss
la source
1

Pour répondre à la deuxième moitié de votre question: oui, vous le pouvez, et un schéma assez courant en C est le suivant:

typedef struct {
    size_t numElements
    int elements[1]; /* but enough space malloced for numElements at runtime */
} IntArray_t;

#define SIZE 10
IntArray_t* myArray = malloc(sizeof(intArray_t) + SIZE * sizeof(int));
myArray->numElements = SIZE;
MSalters
la source
C'est une technique complètement différente de celle que BSD malloc utilise pour les petits objets (bien que ce soit une technique parfaitement bonne pour créer des tableaux de style Pascal)
Pete Kirkham
0

Lorsque nous appelons malloc, cela consomme simplement plus d'octets par rapport à son exigence. Cette consommation de plus d'octets contient des informations telles que la somme de contrôle, la taille et d'autres informations supplémentaires. Lorsque nous appelons gratuitement à ce moment-là, il va directement à cette information supplémentaire où il trouve l'adresse et trouve également combien de bloc sera libre.

Varun Chhangani
la source
0

pour répondre à la deuxième question, oui, vous pouvez (en quelque sorte) utiliser la même technique qu'en malloc() affectant simplement la première cellule de chaque tableau à la taille du tableau. qui vous permet d'envoyer le tableau sans envoyer d'argument de taille supplémentaire.

avish
la source