292 votes

Comment fonctionnent les fonctions malloc() et free() ?

Je veux savoir comment malloc y free travail.

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 approfondie au niveau de la mémoire, si c'est possible.

5 votes

Ne devrait-il pas en fait dépendre du compilateur et de la bibliothèque d'exécution utilisés ?

9 votes

Cela dépendra de l'implémentation du CRT. Vous ne pouvez donc pas le généraliser.

0 votes

Il y a un exemple d'implémentation de malloc() y free() dans Le Livre (Kernighan et Ritchie) " Le langage de programmation C "). Puisque vous avez dû le demander, vous ne l'avez pas lu - allez-y, lisez-le, et repentez-vous de vos péchés. :D

409voto

Juergen Points 4839

OK, certaines réponses concernant malloc ont déjà été postées.

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

Dans de nombreuses implémentations de malloc/free, free ne renvoie normalement pas la mémoire au système d'exploitation (ou du moins seulement dans de rares cas). La raison en est que vous aurez des vides dans votre tas et qu'il peut donc arriver que vous finissiez vos 2 ou 4 Go de mémoire virtuelle avec des vides. Il faut éviter cela, car dès que la mémoire virtuelle est terminée, vous aurez de gros problèmes. L'autre raison est que le système d'exploitation ne peut gérer que des morceaux de mémoire d'une taille et d'un alignement spécifiques. Pour être plus 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 4KB).

Ainsi, renvoyer 40 octets au système d'exploitation ne fonctionnera pas. Alors que fait free ?

Free placera le bloc mémoire dans sa propre liste de blocs libres. Normalement, il essaie également de réunir les blocs adjacents dans l'espace d'adressage. La liste des blocs libres n'est qu'une liste circulaire de morceaux de mémoire qui contiennent des données administratives au début. 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 morceau de mémoire a besoin de données supplémentaires et plus la taille est petite, plus la fragmentation est importante.

La liste libre est également le premier endroit que malloc consulte lorsqu'un nouveau morceau de mémoire est nécessaire. Elle est parcourue avant de demander de la mémoire au système d'exploitation. Lorsqu'un morceau est trouvé qui est plus grand que la mémoire nécessaire, il est divisé en deux parties. L'une est renvoyée à l'appelant, l'autre est remise dans la liste libre.

Il existe de nombreuses optimisations différentes de ce comportement standard (par exemple pour les petits morceaux de mémoire). Mais puisque malloc et free doivent être si universels, le comportement standard est toujours la solution de repli lorsque les alternatives ne sont pas utilisables. Il y a aussi des optimisations dans la gestion de la free-list - par exemple en stockant les morceaux dans des listes triées par tailles. Mais toutes les optimisations ont aussi leurs propres limites.

Pourquoi votre code se plante :

La raison est qu'en écrivant 9 caractères (n'oubliez pas l'octet nul de fin) dans une zone dimensionnée pour 4 caractères, vous allez probablement écraser les données administratives stockées pour un autre morceau de mémoire qui se trouve "derrière" votre morceau de données (puisque ces données sont le plus souvent stockées "devant" les morceaux de mémoire). Lorsque free essaiera ensuite de placer votre bloc dans la liste libre, il pourra toucher ces données administratives et donc trébucher sur un pointeur écrasé. Cela fera 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 où le système n'a pas planté immédiatement mais quelques sous-routines plus tard. Même dans un système de complexité moyenne, de tels problèmes peuvent être vraiment, vraiment difficiles à déboguer ! Dans le cas où j'ai été impliqué, il nous a fallu (un groupe plus important de développeurs) plusieurs jours pour trouver la raison du plantage -- car il se trouvait à un endroit totalement différent de celui indiqué par le vidage de la mémoire. C'est comme une bombe à retardement. Vous savez que votre prochain "free" ou "malloc" va planter, mais vous ne savez pas pourquoi !

Ce sont là certains des pires problèmes du C/C++, et l'une des raisons pour lesquelles les pointeurs peuvent être si problématiques.

69 votes

Il y a tellement de gens qui ne réalisent pas que free() peut ne pas rendre la mémoire au système d'exploitation, c'est exaspérant. Merci d'aider à les éclairer.

0 votes

Artelius : au contraire, la nouvelle volonté fait toujours ?

4 votes

@Guillaume07 Je suppose que vous vouliez dire "supprimer" et non "nouveau". Non, ce n'est pas le cas (nécessairement). delete et free font (presque) la même chose. Voici le code que chacun appelle dans MSVC2013 : goo.gl/3O2Kyu

59voto

joe Points 7966

Comme le dit aluser dans ce fil du forum :

Votre processus possède une région de mémoire, de l'adresse x à l'adresse y, appelée le tas. Toutes vos données mallocées vivent dans cette zone. malloc() conserve une structure de données, disons une liste, de tous les morceaux de mémoire libres dans le tas. d'espace dans le tas. Quand vous appelez malloc, il cherche dans la liste liste pour trouver un morceau assez grand pour vous, renvoie un pointeur vers lui et enregistre le fait qu'il n'est pas assez grand pour vous. enregistre le fait qu'il n'est plus libre ainsi que sa taille. Quand vous appelez free() avec le même pointeur, free() regarde la taille de ce morceau et y ajoute la taille de l'objet. ce morceau et le rajoute dans la liste des morceaux libres(). Si vous Si vous appelez malloc() et qu'il ne trouve pas de morceau assez grand dans le tas, il utilise l'appel système brk() pour agrandir le tas, c'est-à-dire augmenter l'adresse y et faire et faire en sorte que toutes les adresses situées entre l'ancienne et la nouvelle adresse y soient de la mémoire valide. mémoire valide. brk() doit être un appel système ; il n'y a aucun moyen de faire la même chose entièrement à partir entièrement depuis l'espace utilisateur.

malloc() dépend du système et du compilateur, il est donc difficile de donner une réponse précise. Cependant, il garde la trace de la mémoire qu'il a allouée et selon la façon dont il le fait, vos appels à free peuvent échouer ou réussir.

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

1 votes

C'est pourquoi on appelle ça un comportement indéfini. Une implémentation pourrait faire sortir des démons de votre nez lorsque vous appelez free après une écriture invalide. On ne sait jamais.

40voto

samoz Points 14652

Une implémentation de malloc/free fait ce qui suit :

  1. Obtenir un bloc de mémoire du système d'exploitation à travers 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 du bloc suivant et précédent.
  3. Lorsqu'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 pieds de page sont mis à jour en conséquence.

28voto

DigitalRoss Points 80400

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

Votre code d'exemple demande essentiellement pourquoi le programme d'exemple ne piège 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 la bibliothèque et gère sans application des blocs de taille arbitraire qui sont souvent beaucoup plus petits que les pages.

La mémoire ne peut être retirée de votre programme qu'en unités de pages, et même cela a peu de chances d'être 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 noyau. 1 ils l'ajoutent simplement à une liste libre que calloc() et malloc() consulteront plus tard afin de réutiliser les blocs libérés.

Même si un free() voulait rendre de la mémoire au système, il aurait besoin d'au moins une page de mémoire contiguë pour que le noyau protège effectivement la région, de sorte que la libération d'un petit bloc n'entraînerait un changement de protection que s'il s'agissait de la page de mémoire contiguë. dernier petit bloc dans une page.

Donc, votre bloc est là, sur la liste libre. Vous pouvez presque toujours y accéder et accéder à la mémoire voisine comme s'il était encore alloué. Le C se compile directement en code machine et, sans dispositions spéciales de débogage, il n'y a pas de contrôle d'intégrité sur les chargements et les stockages. Maintenant, si vous essayez d'accéder à un bloc libre, le comportement est indéfini par la norme afin de ne pas imposer des exigences déraisonnables aux implémenteurs de bibliothèques. Si vous essayez d'accéder à la mémoire libérée ou à la mémoire en dehors d'un bloc alloué, il y a plusieurs choses qui peuvent mal tourner :

  • 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 "footer", je suppose) votre bloc, mais il se peut qu'ils veuillent utiliser la mémoire à l'intérieur du bloc dans le but de garder la liste libre liée ensemble. Si c'est le cas, vous pouvez lire le bloc sans problème, mais son contenu peut changer, et écrire dans le bloc pourrait provoquer un mauvais comportement ou un plantage de l'allocateur.
  • Naturellement, votre bloc peut être alloué dans le futur, et alors il est probable qu'il soit écrasé par votre code ou une routine de bibliothèque, ou avec des zéros par calloc().
  • Si le bloc est réalloué, sa taille peut également être modifiée, auquel cas d'autres liens ou initialisations seront écrits à divers endroits.
  • Il est évident que vous pouvez vous référer si loin de la plage que vous franchissez la limite d'un des segments connus du noyau de votre programme, et dans ce cas, vous serez piégé.

Théorie du fonctionnement

Donc, en partant de votre exemple pour arriver à la théorie générale, malloc(3) obtient de la mémoire du noyau quand il en a besoin, et 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 coalescent les blocs libres adjacents lorsque cela est possible afin de pouvoir fournir de grands blocs. Le répertoire peut ou non impliquer l'utilisation de la mémoire dans les blocs libres pour former une liste liée. (L'alternative est un peu plus favorable à la mémoire partagée et à la pagination, et elle implique l'allocation de mémoire spécifiquement pour le répertoire). Malloc et free ont peu ou pas de capacité à imposer l'accès à des blocs individuels, même si un code de débogage spécial et optionnel est compilé dans le programme.


1. Le fait que très peu d'implémentations de free() tentent de restituer de la mémoire au système n'est pas nécessairement dû à un relâchement de la part des implémenteurs. L'interaction avec le noyau est beaucoup plus lente que la simple exécution du code de la bibliothèque, et le bénéfice serait faible. La plupart des programmes ont une empreinte mémoire stable ou croissante, donc le temps passé à analyser le tas à la recherche de mémoire retournable serait complètement perdu. D'autres raisons incluent le fait que la fragmentation interne rend improbable l'existence de blocs alignés sur une page, et il est probable que le retour d'un bloc fragmenterait les blocs des deux côtés. Enfin, les quelques programmes qui retournent de grandes quantités de mémoire sont susceptibles de contourner malloc() et de simplement allouer et libérer des pages de toute façon.

0 votes

Bonne réponse. Je recommande le document : Allocation dynamique de stockage : A survey and Critical review par Wilson et al pour un examen approfondi des mécanismes internes, tels que les champs d'en-tête et les listes libres, qui sont utilisés par les allocateurs.

23voto

Chris Arguin Points 6469

En théorie, malloc obtient de la mémoire du système d'exploitation pour cette application. Cependant, étant donné que vous pouvez ne vouloir que 4 octets et que le système d'exploitation doit travailler par pages (souvent 4k), malloc fait un peu plus que cela. Il prend une page, et y met ses propres informations afin de pouvoir garder la 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 de 8 à 12 octets avant vos 4 octets sont utilisés par malloc pour faire une chaîne de toute la mémoire que vous avez allouée. Lorsque vous appelez free, il prend votre pointeur, recule jusqu'à l'endroit où se trouvent les données, et opère sur celles-ci.

Lorsque vous libérez de la mémoire, malloc retire ce bloc de mémoire de la chaîne... et peut ou non retourner cette mémoire au système d'exploitation. Si c'est le cas, l'accès à cette mémoire échouera probablement, car le système d'exploitation vous retirera les permissions d'accès à cet emplacement. Si malloc garde la mémoire (parce qu'il a d'autres choses allouées dans cette page, ou pour une certaine optimisation), alors l'accès fonctionnera. C'est toujours faux, mais ça peut fonctionner.

AVERTISSEMENT : Ce que j'ai décrit est une implémentation courante de malloc, mais en aucun cas la seule possible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X