Je veux savoir comment malloc
et free
travailler.
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.
malloc
dans Ccout <<
Réponses:
OK, quelques réponses sur malloc ont déjà été publiées.
La partie la plus intéressante est le fonctionnement de la gratuité (et dans ce sens, malloc aussi peut ê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 seulement dans de rares cas). La raison en est que vous obtiendrez des lacunes dans votre tas et donc cela peut arriver, que vous venez de terminer vos 2 ou 4 Go de mémoire virtuelle avec des lacunes. Cela devrait être évité, car dès que la mémoire virtuelle sera terminée, vous aurez de très gros ennuis. L'autre raison est que le système d'exploitation ne peut gérer que des blocs de mémoire d'une taille et d'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 le libre?
Free placera le bloc de mémoire dans sa propre liste de blocs libres. Normalement, il essaie également de fusionner les blocs adjacents dans l'espace d'adressage. La liste de blocage libre n'est qu'une liste circulaire de blocs de mémoire qui contiennent 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 malloc / free standard n'est pas efficace. Chaque bloc de mémoire a besoin de données supplémentaires et avec des tailles plus petites, une plus grande fragmentation se produit.
La liste libre est également le premier endroit que malloc examine lorsqu'un nouveau morceau de mémoire est nécessaire. Il est analysé avant d'appeler une nouvelle mémoire du système d'exploitation. Lorsqu'un bloc est plus grand que la mémoire requise, il est divisé en deux parties. L'un est renvoyé à l'appelant, l'autre est remis dans la liste gratuite.
Il existe de nombreuses optimisations différentes de ce comportement standard (par exemple pour les petits morceaux de mémoire). Mais comme malloc et free doivent être si universels, le comportement standard est toujours le repli lorsque les 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 tailles. Mais toutes les optimisations ont également leurs propres limites.
Pourquoi votre code plante-t-il:
La raison en est qu'en écrivant 9 caractères (n'oubliez pas l'octet nul final) dans une zone de 4 caractères, vous écraserez probablement les données administratives stockées pour un autre bloc de mémoire qui se trouve "derrière" votre bloc de données ( car ces données sont le plus souvent stockées "devant" les morceaux de mémoire). Lorsqu'il est libre, puis essaie de mettre votre morceau dans la liste gratuite, il peut 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 le système n'a pas immédiatement planté, mais certains sous-programmes 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'étais impliqué, il nous a fallu (à un plus grand groupe de développeurs) plusieurs jours pour trouver la raison de l'accident - car il était dans un emplacement totalement différent de celui indiqué par le vidage de la mémoire. C'est comme une bombe à retardement. Vous savez, votre prochain "gratuit" ou "malloc" va planter, mais vous ne savez pas pourquoi!
Ce sont certains des pires problèmes C / C ++, et l'une des raisons pour lesquelles les pointeurs peuvent être si problématiques.
la source
Comme le dit un utilisateur dans ce fil de discussion :
malloc () dépend du système / compilateur, il est donc difficile de donner une réponse spécifique. Fondamentalement, cependant, il garde une trace de la mémoire allouée et, selon la façon dont il le fait, vos appels à libérer pourraient échouer ou réussir.
malloc() and free() don't work the same way on every O/S.
la source
Une implémentation de malloc / free fait ce qui suit:
la source
La protection de la mémoire a une granularité de page et nécessiterait une interaction avec le noyau
Votre exemple de code demande essentiellement pourquoi l'exemple de programme ne s'interrompt 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, tandis 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 qu'en unités de pages, et même cela est peu susceptible 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 , elles l'ajoutent simplement à une liste gratuite que calloc () et malloc () consulteront plus tard afin de réutiliser les blocs libérés.
Même si free () voulait restituer 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 réellement la région, donc libérer un petit bloc ne conduirait à un changement de protection que s'il était le dernier petit bloc d'une page.
Votre bloc est donc là, assis sur la liste gratuite. Vous pouvez presque toujours y accéder et la mémoire à proximité comme si elle était encore allouée. C compile directement en code machine et sans dispositions de débogage spéciales, il n'y a pas de contrôle d'intégrité sur les charges et les magasins. Maintenant, si vous essayez d'accéder à un bloc libre, le comportement n'est pas défini par la norme afin de ne pas imposer de contraintes déraisonnables aux implémenteurs de bibliothèque. Si vous essayez d'accéder à la mémoire libérée ou à la mémoire en dehors d'un bloc alloué, plusieurs choses peuvent mal se passer:
Théorie du fonctionnement
Ainsi, en reculant de votre exemple à la théorie générale, malloc (3) obtient 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 fusionnent les 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 liée. (L'alternative est un peu plus de mémoire partagée et conviviale pour la pagination, et cela implique d'allouer de la mémoire spécifiquement pour le répertoire.) Malloc et free ont peu ou pas de possibilité 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 () tentent de restituer de la mémoire au système n'est pas nécessairement dû au ralentissement des implémenteurs. Interagir avec le noyau est beaucoup plus lent que d'exécuter simplement du code de bibliothèque, et l'avantage serait faible. La plupart des programmes ont une empreinte mémoire stable ou croissante, de sorte que 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 les blocs alignés par page peu susceptibles d'exister, et il est probable que le retour d'un bloc fragmente les blocs de chaque côté. Enfin, les quelques programmes qui renvoient de grandes quantités de mémoire sont susceptibles de contourner malloc () et simplement d'allouer et de libérer des pages de toute façon.
la source
En théorie, malloc obtient la mémoire du système d'exploitation pour cette application. Cependant, comme vous ne voulez que 4 octets et que le système d'exploitation doit fonctionner en pages (souvent 4k), malloc fait un peu plus que cela. Il prend une page et y met ses propres informations afin 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 8-12 octets avant vos 4 octets est utilisée par malloc pour faire une chaîne de toute la mémoire que vous avez allouée. Lorsque vous appelez gratuitement, il prend votre pointeur, sauvegarde là où se trouvent les données et fonctionne sur cela.
Lorsque vous libérez de la mémoire, malloc retire ce bloc de mémoire de la chaîne ... et peut ou non renvoyer 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 supprimera vos autorisations d'accès à cet emplacement. Si malloc conserve la mémoire (car il y a d'autres choses allouées dans cette page, ou pour une certaine optimisation), alors l'accès se passera. C'est toujours faux, mais ça pourrait marcher.
AVERTISSEMENT: Ce que j'ai décrit est une implémentation courante de malloc, mais en aucun cas la seule possible.
la source
Votre ligne strcpy tente de stocker 9 octets, pas 8, à cause du terminateur NUL. Il invoque un comportement indéfini.
L'appel à free peut se bloquer ou non. 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 griffonner partout, cela "mal" va mal, mais s'il n'est pas utilisé pour autre chose, alors vous pourriez vous en tirer. "S'en tirer" peut sembler bon, mais en fait mauvais, car cela signifie que votre code semblera fonctionner correctement, mais lors d'une prochaine exécution, vous ne pourrez peut-être pas vous en sortir.
Avec un allocateur de mémoire de style débogage, vous pouvez constater qu'une valeur de garde spéciale a été écrite là-bas, et que la vérification gratuite de cette valeur et panique si elle ne la trouve pas.
Sinon, vous pourriez constater 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 de l'ajouter à une liste de blocs disponibles, et parce que vous avez griffonné dans le nœud de liste, cette opération pourrait déréférencer un pointeur avec une valeur non valide, provoquant un crash.
Tout dépend de l'allocateur de mémoire - différentes implémentations utilisent différents mécanismes.
la source
Le fonctionnement de malloc () et free () dépend de la bibliothèque d'exécution utilisée. En général, malloc () alloue un tas (un bloc de mémoire) à partir du système d'exploitation. Chaque requête à malloc () alloue ensuite un petit morceau de cette mémoire en renvoyant 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 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 détruisez très probablement certaines des informations de tenue de livres du bloc suivant qui peut être le bloc de mémoire inutilisé restant.
Un endroit où votre programme peut également planter est lors de la copie d'un trop grand nombre de caractères dans le tampon. Si les caractères supplémentaires se trouvent en dehors du tas, vous pouvez obtenir une violation d'accès lorsque vous essayez d'écrire dans la mémoire inexistante.
la source
Cela n'a rien à voir spécifiquement avec malloc et gratuit. Votre programme présente un comportement indéfini après avoir copié la chaîne - il peut se bloquer à ce stade ou à tout moment par la suite. Cela serait vrai même si vous n'avez jamais utilisé malloc et free, et alloué le tableau char sur la pile ou de manière statique.
la source
malloc et free dépendent de l'implémentation. Une implémentation typique implique de partitionner la mémoire disponible en une "liste libre" - une liste liée de blocs de mémoire disponibles. De nombreuses implémentations le divisent artificiellement en petits et gros objets. Les blocs libres commencent par des informations sur la taille du bloc de mémoire et où se trouve le prochain, etc.
Lorsque vous malloc, un bloc est extrait de la liste gratuite. Lorsque vous libérez, le bloc est remis dans la liste gratuite. Il y a de fortes chances que, lorsque vous écrasez la fin de votre pointeur, vous écrivez sur l'en-tête d'un bloc dans la liste libre. Lorsque vous libérez votre mémoire, free () essaie de regarder le bloc suivant et finit probablement par frapper un pointeur qui provoque une erreur de bus.
la source
Eh bien, cela dépend de l'implémentation de l'allocateur de 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. Il ne s'agit cependant pas de mémoire allouée à votre application. L'allocateur de mémoire CRT marquera la mémoire comme un bloc contigu "disponible". L'allocateur de mémoire CRT parcourra alors la liste des blocs libres et trouvera le plus petit bloc possible qu'il pourra utiliser. Il prendra alors autant de ce bloc que nécessaire et l'ajoutera à une liste "allouée". Un en-tête sera attaché à la tête de l'allocation de mémoire réelle. Cet en-tête contiendra divers bits d'informations (il pourrait, par exemple, contenir les blocs alloués suivants et précédents pour former une liste lié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 libre. S'il forme un bloc plus grand avec les blocs libres environnants, ceux-ci seront additionnés pour donner un bloc plus grand. Si une page entière est désormais libre, l'allocateur retournera très probablement la page au système d'exploitation.
Ce n'est pas un problème simple. La portion d'allocateur de système d'exploitation est complètement hors de votre contrôle. Je vous recommande de lire quelque chose comme Malloc de Doug Lea (DLMalloc) pour comprendre comment un allocateur assez rapide fonctionnera.
Modifier: votre plantage sera causé par le fait qu'en écrivant plus que l'allocation, vous avez écrasé l'en-tête de mémoire suivant. De cette façon, quand il se libère, il est très confus quant à ce qu'il libère exactement et comment fusionner dans le bloc suivant. Cela ne peut pas toujours provoquer immédiatement un crash sur le free. Cela peut provoquer un crash plus tard. En général, évitez les remplacements de mémoire!
la source
Votre programme se bloque car il utilise une mémoire qui ne vous appartient pas. Il peut être utilisé par quelqu'un d'autre ou non - si vous avez de la chance, vous vous écrasez, sinon le problème peut rester caché pendant longtemps et revenir et vous mordre plus tard.
En ce qui concerne la mise en œuvre de malloc / free - des livres entiers sont consacrés au sujet. Fondamentalement, l'allocateur obtiendrait de plus gros morceaux de mémoire du système d'exploitation et les gérerait pour vous. Certains des problèmes qu'un allocateur doit résoudre sont les suivants:
la source
C'est difficile à dire car le comportement réel est différent entre différents compilateurs / runtimes. Même les versions de débogage / version ont un comportement différent. Les versions de débogage de VS2005 insèrent des marqueurs entre les allocations pour détecter la corruption de mémoire, donc au lieu d'un plantage, il s'affirmera dans free ().
la source
Il est également important de réaliser que le simple fait de déplacer le pointeur de rupture de programme avec
brk
et n'allouesbrk
pas réellement la mémoire, il configure simplement l'espace d'adressage. Sous Linux, par exemple, la mémoire sera "sauvegardée" par des pages physiques réelles lors de l'accès à cette plage d'adresses, ce qui entraînera une erreur de page et conduira finalement le noyau à appeler l'allocateur de page pour obtenir une page de sauvegarde.la source