web-dev-qa-db-fra.com

Comment fonctionnent malloc () et free ()?

Je veux savoir comment malloc et free fonctionnent.

int main() {
    unsigned char *p = (unsigned char*)malloc(4*sizeof(unsigned char));
    memset(p,0,4);
    strcpy((char*)p,"abcdabcd"); // **deliberately storing 8bytes**
    cout << p;
    free(p); // Obvious Crash, but I need how it works and why crash.
    cout << p;
    return 0;
}

Je serais vraiment reconnaissant si la réponse est en profondeur au niveau de la mémoire, si cela est possible.

261
mahesh

OK, des réponses à propos de malloc ont déjà été postées.

La partie la plus intéressante est comment free fonctionne (et dans ce sens, malloc peut aussi être mieux compris).

Dans de nombreuses implémentations malloc/free, free ne renvoie normalement pas la mémoire au système d'exploitation (ou du moins, dans de rares cas). La raison en est que vous obtiendrez des espaces vides dans votre segment de mémoire et que cela peut donc arriver, vous venez de terminer vos 2 ou 4 Go de mémoire virtuelle avec des espaces vides. Cela devrait être évité, car dès que la mémoire virtuelle sera terminée, vous aurez de gros problèmes. L'autre raison est que le système d'exploitation ne peut gérer que des fragments de mémoire ayant une taille et un alignement spécifiques. Pour être précis: Normalement, le système d'exploitation ne peut gérer que les blocs que le gestionnaire de mémoire virtuelle peut gérer (le plus souvent des multiples de 512 octets, par exemple 4 Ko).

Donc, retourner 40 octets au système d'exploitation ne fonctionnera tout simplement pas. Alors, que fait Free?

Free mettra le bloc de mémoire dans sa propre liste de blocs libres. Normalement, il essaie également de fusionner des blocs adjacents dans l'espace d'adressage. La liste de blocage disponible est simplement une liste circulaire de morceaux de mémoire contenant au début des données administratives. C’est aussi la raison pour laquelle la gestion de très petits éléments de mémoire avec le standard malloc/free n’est pas efficace. Chaque bloc de mémoire nécessite des données supplémentaires et, avec des tailles plus petites, une fragmentation accrue se produit.

La liste libre est également le premier endroit que malloc examine lorsqu'un nouveau bloc de mémoire est nécessaire. Il est analysé avant de demander une nouvelle mémoire à partir du système d'exploitation. Lorsqu'un morceau trouvé est plus grand que la mémoire nécessaire, il est divisé en deux parties. L'un est renvoyé à l'appelant, l'autre est réinséré dans la liste des numéros disponibles.

Il existe de nombreuses optimisations pour ce comportement standard (par exemple, pour de petits morceaux de mémoire). Mais comme malloc et free doivent être si universels, le comportement standard est toujours le repli lorsque des alternatives ne sont pas utilisables. Il existe également des optimisations dans la gestion de la liste libre - par exemple, le stockage des morceaux dans des listes triées par taille. Mais toutes les optimisations ont aussi leurs propres limites.

Pourquoi votre code plante-t-il:

La raison en est qu’en écrivant 9 caractères (n'oubliez pas le dernier octet) dans une zone de 4 caractères, vous écraserez probablement les données administratives stockées pour un autre bloc de mémoire situé "derrière" votre bloc de données ( puisque ces données sont le plus souvent stockées "devant" des morceaux de mémoire). Lorsque free essaie alors de mettre votre bloc dans la liste des disponibilités, il peut toucher ces données administratives et donc tomber sur un pointeur écrasé. Cela va planter le système.

C'est un comportement plutôt gracieux. J'ai également vu des situations où un pointeur en fuite, quelque part, a écrasé des données dans la liste sans mémoire et que le système ne s'est pas immédiatement bloqué, mais des sous-routines plus tard. Même dans un système de complexité moyenne, de tels problèmes peuvent être vraiment, vraiment difficiles à résoudre! Dans l'un des cas où j'ai été impliqué, il nous a fallu plusieurs jours pour trouver la raison de l'accident (un groupe plus important de développeurs), car l'emplacement était totalement différent de celui indiqué par le vidage de la mémoire. C'est comme une bombe à retardement. Vous savez, votre prochain "libre" ou "malloc" va planter, mais vous ne savez pas pourquoi!

Ce sont quelques-uns des pires problèmes de C/C++, et l'une des raisons pour lesquelles les pointeurs peuvent être si problématiques.

368
Juergen

Comme aluser le dit dans ce sujet du forum :

Votre processus a une région de mémoire allant de l'adresse x à l'adresse y, appelée le segment de mémoire. Toutes vos données malloc'd vivent dans cette zone. malloc () conserve une structure de données, disons une liste, de tous les morceaux d'espace libre dans le tas. Lorsque vous appelez malloc, il recherche dans la liste un morceau suffisamment volumineux, y renvoie un pointeur et enregistre le fait qu'il n'est plus gratuit, mais aussi grand. Lorsque vous appelez free () avec le même pointeur, free () recherche la taille de ce morceau et l'ajoute à la liste des morceaux gratuits (). Si vous appelez malloc () et qu'il ne trouve pas un bloc suffisamment grand dans le tas, il utilise l'appel système brk () pour agrandir le tas, c'est-à-dire augmenter l'adresse y et faire en sorte que toutes les adresses entre l'ancien y et le nouveau y être valide mémoire. brk () doit être un appel système; il n'y a aucun moyen de faire la même chose entièrement à partir de l'espace utilisateur.

malloc () dépend du système et du compilateur, il est donc difficile de donner une réponse précise. Cependant, en gros, il garde une trace de la mémoire allouée et, en fonction de la façon dont il le fait, vos appels à libérer pourraient échouer ou aboutir.

malloc() and free() don't work the same way on every O/S.

54
joe

Une implémentation de malloc/free effectue les opérations suivantes:

  1. Obtenez un bloc de mémoire du système d'exploitation via sbrk () (appel Unix).
  2. Créez un en-tête et un pied de page autour de ce bloc de mémoire avec des informations telles que la taille, les autorisations et l'emplacement des blocs suivant et précédent.
  3. Quand un appel à malloc arrive, une liste est référencée qui pointe vers des blocs de la taille appropriée.
  4. Ce bloc est ensuite renvoyé et les en-têtes et les pieds de page sont mis à jour en conséquence.
34
samoz

La protection de la mémoire a une granularité de page et nécessiterait une interaction du noyau

Votre code exemple demande essentiellement pourquoi le programme exemple ne déroute pas, et la réponse est que la protection de la mémoire est une fonctionnalité du noyau et ne s'applique qu'à des pages entières, alors que l'allocateur de mémoire est une fonctionnalité de bibliothèque et qu'il gère .. sans application .. arbitraire blocs de taille qui sont souvent beaucoup plus petits que les pages.

La mémoire ne peut être supprimée de votre programme que par unités de pages et il est peu probable que cela soit observé.

calloc (3) et malloc (3) interagissent avec le noyau pour obtenir de la mémoire, si nécessaire. Mais la plupart des implémentations de free (3) ne renvoient pas de mémoire au noyau1, ils viennent juste de l’ajouter à une liste libre que calloc () et malloc () consulteront ultérieurement afin de réutiliser les blocs libérés.

Même si free () souhaitait restituer de la mémoire au système, il aurait besoin d'au moins une page de mémoire contiguë pour que le noyau puisse réellement protéger la région. Par conséquent, la publication d'un petit bloc entraînerait un changement de protection uniquement. le dernier petit bloc dans une page.

Donc, votre bloc est là, assis sur la liste libre. Vous pouvez presque toujours y accéder et accéder à la mémoire proche comme si elle était toujours allouée. C est compilé directement dans le code machine et sans dispositions de débogage spéciales, il n’ya pas de contrôle de cohérence des charges et des magasins. Désormais, si vous essayez d'accéder à un bloc libre, le comportement n'est pas défini par la norme afin de ne pas imposer des exigences déraisonnables aux développeurs de bibliothèques. Si vous essayez d'accéder à de la mémoire libérée ou à de la mémoire en dehors d'un bloc alloué, plusieurs problèmes peuvent se produire:

  • Parfois, les allocateurs maintiennent des blocs de mémoire séparés, parfois ils utilisent un en-tête qu’ils allouent juste avant ou après (un "pied de page", je suppose) de votre bloc, mais ils peuvent simplement vouloir utiliser de la mémoire dans le bloc pour conserver la liste libre. liés ensemble. Si tel est le cas, votre lecture du bloc est correcte, mais son contenu peut changer et une écriture dans le bloc risquerait de provoquer un dysfonctionnement de l'allocateur ou un blocage.
  • Naturellement, votre bloc peut être alloué à l'avenir, et il est alors probable qu'il soit écrasé par votre code ou une routine de bibliothèque, ou par zéros par calloc ().
  • Si le bloc est réaffecté, sa taille peut également être modifiée, auquel cas davantage de liens ou d'initialisations seront écrits à divers endroits.
  • Évidemment, vous pouvez faire référence à une distance si éloignée de la limite que vous franchissez la limite d’un des segments connus du noyau de votre programme, et dans ce cas, vous allez intercepter.

Théorie de fonctionnement

Donc, en reprenant votre exemple par rapport à la théorie générale, malloc (3) tire la mémoire du noyau quand il en a besoin, généralement en unités de pages. Ces pages sont divisées ou consolidées selon les besoins du programme. Malloc et Free coopèrent pour maintenir un répertoire. Ils fusionnent des blocs libres adjacents lorsque cela est possible afin de pouvoir fournir de gros blocs. Le répertoire peut impliquer ou non l’utilisation de la mémoire dans des blocs libérés pour former une liste chaînée. (L'alternative est un peu plus conviviale en mémoire partagée et en pagination, et implique d'allouer de la mémoire spécifiquement pour l'annuaire.) Malloc et free ont peu de possibilité, voire aucune, d'imposer l'accès à des blocs individuels même lorsque du code de débogage spécial et facultatif est compilé dans le programme.


1. Le fait que très peu d'implémentations de free () tente de restituer de la mémoire au système n'est pas nécessairement dû au relâchement des implémenteurs. Interagir avec le noyau est beaucoup plus lent que la simple exécution du code de bibliothèque, et les avantages seraient minimes. La plupart des programmes ont une empreinte mémoire constante ou croissante, de sorte que le temps passé à analyser le tas à la recherche de mémoire restituable serait complètement perdu. D'autres raisons incluent le fait que la fragmentation interne rend peu probable l'existence de blocs alignés sur une page et qu'il est probable que le renvoi d'un bloc fragmenterait des blocs d'un côté ou de l'autre. Enfin, les quelques programmes qui renvoient de grandes quantités de mémoire risquent de contourner malloc () et tout simplement d’allouer et de libérer des pages.

26
DigitalRoss

En théorie, malloc obtient de la mémoire du système d'exploitation pour cette application. Cependant, comme vous ne voulez peut-être que 4 octets et que le système d'exploitation doit fonctionner en pages (souvent 4 ko), malloc fait un peu plus que cela. Il faut une page et y insère ses propres informations pour qu'il puisse garder une trace de ce que vous avez alloué et libéré de cette page.

Lorsque vous allouez 4 octets, par exemple, malloc vous donne un pointeur sur 4 octets. Ce que vous ne réalisez peut-être pas, c’est que la mémoire utilise 8-12 octets avant, vos 4 octets sont utilisés par Malloc pour créer une chaîne de la mémoire allouée. Lorsque vous appelez gratuitement, il prend votre pointeur, enregistre là où sont ses données et fonctionne sur cette base.

Lorsque vous libérez de la mémoire, Malloc supprime ce bloc de mémoire de la chaîne ... et peut ou non rendre cette mémoire au système d'exploitation. Si tel est le cas, l'accès à cette mémoire échouera probablement, car le système d'exploitation vous enlèvera vos autorisations pour accéder à cet emplacement. Si malloc conserve la mémoire (car d'autres éléments sont affectés à cette page, ou pour une optimisation), l'accès se passera alors bien. C'est toujours faux, mais ça pourrait marcher.

CLAUSE DE NON-RESPONSABILITÉ: Ce que j'ai décrit est une implémentation courante de malloc, mais pas la seule possible.

24
Chris Arguin

Votre ligne strcpy tente de stocker 9 octets, et non 8, à cause du terminateur NUL. Il appelle un comportement indéfini.

L'appel à libérer peut ou ne peut pas se bloquer. La mémoire "après" les 4 octets de votre allocation peut être utilisée pour autre chose par votre implémentation C ou C++. S'il est utilisé pour autre chose, alors gribouiller partout, cela fera que "quelque chose d'autre" va mal, mais s'il n'est pas utilisé pour autre chose, alors vous pourriez arriver à vous en tirer. "S'éloigner avec cela" peut sembler bon, mais c'est en fait mauvais, car cela signifie que votre code semblera fonctionner correctement, mais vous ne vous en tirerez peut-être pas plus tard.

Avec un allocateur de mémoire de type débogage, vous constaterez peut-être qu’une valeur de garde spéciale a été écrite à cet endroit, que Free vérifie cette valeur et panique s’il ne la trouve pas.

Sinon, vous constaterez peut-être que les 5 octets suivants incluent une partie d'un nœud de liaison appartenant à un autre bloc de mémoire qui n'a pas encore été alloué. La libération de votre bloc pourrait bien impliquer l'ajout à une liste de blocs disponibles et, comme vous avez gribouillé dans le nœud de liste, cette opération pourrait déréférencer un pointeur avec une valeur non valide, ce qui provoquerait un crash.

Tout dépend de l'allocateur de mémoire - différentes implémentations utilisent différents mécanismes.

12
Steve Jessop

Le fonctionnement de malloc () et de free () dépend de la bibliothèque d'exécution utilisée. En général, malloc () alloue un segment de mémoire (un bloc de mémoire) à partir du système d'exploitation. Chaque demande à malloc () alloue ensuite une petite partie de cette mémoire pour renvoyer un pointeur à l'appelant. Les routines d'allocation de mémoire devront stocker des informations supplémentaires sur le bloc de mémoire alloué pour pouvoir garder une trace de la mémoire utilisée et de la mémoire libre sur le tas. Ces informations sont souvent stockées dans quelques octets juste avant le pointeur renvoyé par malloc () et il peut s'agir d'une liste chaînée de blocs de mémoire.

En écrivant au-delà du bloc de mémoire alloué par malloc (), vous allez probablement détruire une partie des informations de comptabilité du bloc suivant, qui peut être le bloc de mémoire inutilisé restant.

Vous risquez également de provoquer une panne lors de la programmation lorsque vous copiez trop de caractères dans la mémoire tampon. Si les caractères supplémentaires se trouvent en dehors du segment de mémoire, vous risquez d'obtenir une violation d'accès lorsque vous essayez d'écrire dans une mémoire non existante.

12
Martin Liversage

Cela n'a rien à voir avec malloc et gratuit. Votre programme présente un comportement indéfini après la copie de la chaîne. Il pourrait se bloquer à ce moment-là ou ultérieurement. Cela serait vrai même si vous n'avez jamais utilisé malloc et free, et alloué le tableau de caractères sur la pile ou de manière statique.

6
anon

malloc et free dépendent de la mise en œuvre. Une implémentation typique implique de partitionner la mémoire disponible en une "liste libre" - une liste chaînée de blocs de mémoire disponibles. De nombreuses implémentations le divisent artificiellement en petits objets vs grands. Les blocs libres commencent par des informations sur la taille du bloc de mémoire et l'emplacement du prochain, etc.

Lorsque vous malloc, un bloc est tiré de la liste libre. Lorsque vous libérez, le blocage est remis dans la liste des libres. Les chances sont, lorsque vous écrasez la fin de votre pointeur, vous écrivez sur l'en-tête d'un bloc dans la liste libre. Quand vous libérez votre mémoire, free () essaie de regarder le bloc suivant et finit probablement par frapper un pointeur qui cause une erreur de bus.

5
plinth

Cela dépend de l'implémentation de la mémoire et du système d'exploitation.

Sous Windows par exemple, un processus peut demander une page ou plus de RAM. Le système d'exploitation affecte ensuite ces pages au processus. Ce n'est cependant pas la mémoire allouée à votre application. L'allocateur de mémoire du tube cathodique marquera la mémoire en tant que bloc "disponible" contigu. L'allocateur de mémoire CRT parcourt ensuite la liste des blocs libres et recherche le plus petit bloc possible qu'il puisse utiliser. Il faudra ensuite prendre autant de ce bloc qu’il en a besoin et l’ajouter à une liste "allouée". Un en-tête est attaché à la tête de l'allocation de mémoire réelle. Cet en-tête contiendra diverses informations (il pourrait, par exemple, contenir les blocs alloués suivants et précédents pour former une liste chaînée. Il contiendra très probablement la taille de l'allocation).

Free supprimera alors l'en-tête et l'ajoutera à la liste de mémoire disponible. S'il forme un bloc plus grand avec les blocs libres environnants, ceux-ci seront ajoutés pour donner un bloc plus grand. Si une page entière est maintenant libre, l'allocateur renverra très probablement la page au système d'exploitation.

Ce n'est pas un problème simple. La partie allocateur du système d'exploitation est complètement hors de votre contrôle. Je vous recommande de lire quelque chose comme Malloc (DLMalloc) de Doug Lea pour comprendre le fonctionnement d'un allocateur assez rapide.

Edit: Votre plantage sera causé par le fait qu'en écrivant plus grand que l'allocation, vous avez écrasé l'en-tête de mémoire suivant. De cette façon, quand il se libère, il devient très confus quant à savoir exactement ce qu’il libère et comment fusionner dans le bloc suivant. Cela ne peut pas toujours provoquer un crash tout de suite sur le free. Cela pourrait causer un crash plus tard. En général, évitez les écrasements de mémoire!

4
Goz

Votre programme se bloque car il utilisait une mémoire qui ne vous appartenait pas. Il peut être utilisé par quelqu'un d'autre ou non - si vous êtes chanceux, vous risquez de vous écraser, sinon le problème risque de rester caché pendant un long moment et de revenir vous piquer plus tard.

En ce qui concerne malloc/free implementation, des livres entiers sont consacrés à ce sujet. Fondamentalement, l’allocateur obtiendrait de plus gros morceaux de mémoire du système d’exploitation et les gérerait pour vous. Certains problèmes qu'un allocateur doit résoudre sont les suivants:

  • Comment obtenir une nouvelle mémoire
  • Comment le stocker - (liste ou autre structure, plusieurs listes pour des morceaux de mémoire de taille différente, etc.)
  • Que faire si l'utilisateur demande plus de mémoire que ce qui est actuellement disponible (demander plus de mémoire à l'OS, joindre certains des blocs existants, comment les joindre exactement, ...)
  • Que faire lorsque l'utilisateur libère de la mémoire
  • Les allocateurs de débogage peuvent vous donner le plus gros morceau que vous avez demandé et lui donner une structure d'octets. Lorsque vous libérez la mémoire, il peut vérifier s'il est écrit en dehors du bloc (ce qui se produit probablement dans votre cas) ...
3
devdimi

C'est difficile à dire car le comportement réel est différent entre différents compilateurs/runtimes. Même les versions de débogage/libération ont un comportement différent. Les versions de débogage de VS2005 inséreront des marqueurs entre les allocations pour détecter la corruption de mémoire. Ainsi, au lieu d’un crash, elles s'affirmeront dans free ().

2
Sebastiaan M

Il est également important de réaliser que déplacer simplement le pointeur de rupture de programme avec brk et sbrk ne fait pas allouer la mémoire, il configure simplement l'espace d'adressage. Sous Linux, par exemple, la mémoire sera "sauvegardée" par les pages physiques réelles lors de l'accès à cette plage d'adresses, ce qui entraînera une erreur de page et conduira éventuellement au noyau à appeler la page d'allocation pour obtenir une page de sauvegarde.

1
mgalgs