87 votes

Pourquoi est-ce que "libre" en C de ne pas prendre le nombre d'octets à être libéré?

Juste pour être clair: je ne sais qu' malloc et free sont mis en œuvre dans la bibliothèque C, qui habituellement alloue des blocs de mémoire à partir de l'OS, et fait de son propre gestion à la parcelle de plus petite taille par beaucoup de mémoire de l'application et assure le suivi du nombre d'octets alloués. Cette question n'est pas Comment gratuit savoir de combien gratuit.

Plutôt, je veux savoir pourquoi free a été fait de cette façon en premier lieu. Étant un langage de bas niveau, je pense qu'il serait tout à fait raisonnable de demander à un programmeur en C pour garder une trace de ce qu'est la mémoire a été allouée, mais combien (en fait, j'ai souvent trouver que je l'conserver une trace du nombre d'octets malloced de toute façon). Il se produit également pour moi que donnant explicitement le nombre d'octets à free pourrait permettre une optimisation des performances, par exemple, un allocateur qui a des piscines séparées pour les différents répartition des tailles serait en mesure de déterminer la piscine gratuit de juste en regardant les arguments d'entrée, et il y aurait moins d'espace supplémentaire dans l'ensemble.

Donc, en résumé, pourquoi n'ont - malloc et free créés tels qu'ils sont requis à l'interne et pour garder une trace du nombre d'octets alloués? Est-ce juste un hasard historique?

Une petite modification: Quelques personnes m'ont fait des remarques comme "que faire si vous libérez un montant différent de ce que vous avez alloué". Mon imaginé API pouvait se contenter de demander un gratuit exactement le nombre d'octets alloués; la libération de plus ou de moins peut être tout simplement l'AC ou de la mise en œuvre définies. Je ne veux pas décourager la discussion sur d'autres possibilités.

100voto

Nathaniel J. Smith Points 1209

Un argument free(void *) (présenté dans Unix V7) a un autre avantage majeur par rapport à la plus tôt deux argument mfree(void *, size_t) que je n'ai pas vu mentionné ici: un argument free simplifie considérablement tous les autres API travaille avec la mémoire du tas. Par exemple, si free besoin de la taille du bloc de mémoire, alors strdup serait en quelque sorte retourner deux valeurs (pointeur + taille) au lieu d'une (pointeur), et C fait multiple de la valeur des rendements beaucoup plus lourd qu'une seule valeur de retour. Au lieu de char *strdup(char *) , nous devrions avoir à écrire char *strdup(char *, size_t *) ou autre struct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *). (Aujourd'hui, la seconde option semble assez tentant, parce que nous savons que NUL des chaînes terminées sont les "plus catastrophique de la conception bug dans l'histoire de l'informatique", mais c'est le recul de parler. Dans les années 70, C est la capacité de manipuler les chaînes de caractères comme un simple char * a été considérée comme une définition d'avantage sur des concurrents comme Pascal et Algol.) De Plus, il n'est pas seulement strdup qui souffre de ce problème, elle affecte tout le système ou la fonction définie par l'utilisateur qui alloue de la mémoire de masse.

Les premiers Unix designers ont été très habiles gens, et il ya beaucoup de raisons pourquoi free est mieux que mfree donc, fondamentalement, je pense que la réponse à la question est qu'elle l'a vu et a conçu son système en conséquence. Je doute que vous trouverez des dommages directs enregistrement de ce qui se passait dans leur tête au moment où ils ont pris cette décision. Mais l'on peut imaginer.

Prétendre que vous êtes à l'écriture d'applications en C pour exécuter le V6 Unix, avec ses deux argument mfree. Vous avez réussi jusqu'ici ça va, mais en gardant une trace de ces pointeur de tailles de plus en plus de tracas que vos programmes devenu plus ambitieux et nécessitent de plus en plus l'utilisation de mémoire allouée variables. Mais alors vous avez une idée géniale: au lieu de copier autour de ces size_ts tout le temps, vous pouvez simplement écrire quelques fonctions utilitaires, qui planque de la taille directement à l'intérieur de la mémoire allouée:

void *my_alloc(size_t size) {
    void *block = malloc(sizeof(size) + size);
    *(size_t *)block = size;
    return (void *) ((size_t *)block + 1);
}
void my_free(void *block) {
    block = (size_t *)block - 1;
    mfree(block, *(size_t *)block);
}

Et plus le code que vous écrivez à l'aide de ces nouvelles fonctions, le plus impressionnant qu'elles semblent être. Non seulement ils font de votre code plus facile à écrire, ils ont également rendre votre code plus rapide -- deux choses qui ne vont souvent de pair! Avant de vous les transmettre size_ts autour de tous sur la place, qui a ajouté le CPU pour la copie, et signifiait le déversement de registres plus souvent (esp. pour l'extra arguments de la fonction), et le gaspillage de mémoire (puisque les appels de fonctions imbriquées dans de multiples copies de l' size_t être stockés dans différents stack frames). Dans votre nouveau système, vous avez encore de passer de la mémoire pour stocker l' size_t, mais seulement une fois, et il n'est jamais copié partout. Ceux-ci peuvent sembler petits gains d'efficience, mais gardez à l'esprit que nous parlons des machines haut de gamme avec 256 Ko de RAM.

Ce qui vous rend heureux! Si vous partagez votre truc génial avec les hommes barbus qui travaillent sur la prochaine Unix libérer, mais il ne les rend pas heureux, ça rend triste. Vous voyez, ils étaient juste dans le processus d'ajout d'un tas de nouvelles fonctions utilitaires comme strdup, et ils se rendent compte que les gens à l'aide de votre truc génial de ne pas être en mesure d'utiliser leurs nouvelles fonctions, en raison de leurs nouvelles fonctions tous utiliser la lourdeur pointeur+taille de l'API. Et puis qui vous rend triste aussi, parce que vous réalisez que vous allez avoir à réécrire le bon strdup(char *) fonction de vous-même dans chaque programme que vous écrivez, au lieu d'être en mesure d'utiliser la version du système.

Mais attendez! C'est en 1977, et en arrière la compatibilité ne sera pas inventé pour un autre 5 ans! Et d'ailleurs, personne n'grave en fait utilise cet obscur "Unix" chose avec son nom de la couleur. La première édition de K&R est sur son chemin à l'éditeur maintenant, mais ce n'est pas un problème-il est dit à droite sur la première page que "C fournit pas d'opérations à traiter directement avec des objets composites tels que des chaînes de caractères... il n'y a pas de segment de mémoire...". À ce point dans l'histoire, string.h et malloc sont des extensions fournisseur (!). Donc, suggère Homme Barbu #1, nous pouvons changer, mais nous aimons; pourquoi ne pas simplement déclarer votre délicat allocateur à l' officiel de l'allocateur?

Quelques jours plus tard, l'Homme Barbu #2 voit la nouvelle API et dit hey, attendez, c'est mieux qu'avant, mais c'est toujours les dépenses d'un mot entier par l'allocation de stockage de la taille. Il considère que la prochaine chose à un blasphème. Tout le monde le regarde comme s'il était fou, parce que quoi d'autre pouvez-vous faire? La nuit, il reste fin et invente une nouvelle allocation qui ne stocke pas la taille, mais plutôt le déduit à la volée par un spectacle de magie noire bitshifts sur la valeur du pointeur, et des swaps dans tout en gardant la nouvelle API en place. La nouvelle API signifie que personne ne remarque l'interrupteur, mais ils font remarquer que le lendemain matin, le compilateur utilise 10% moins de mémoire vive.

Et maintenant tout le monde est content: Vous obtenez votre plus facile à écrire et à accélérer l'exécution du code, Homme Barbu #1 arrive à écrire une simple et agréable, strdup que les gens vont réellement utiliser, et l'Homme Barbu #2 -- persuadé qu'il a gagné sa garder pour un peu, remonte à déconner avec les quines. Ship it!

Ou au moins, c'est comment il pourrait avoir lieu.

31voto

"Pourquoi est - free dans C de ne pas prendre le nombre d'octets à être libéré?"

Car il n'y a aucun besoin, et il ne serait pas tout à fait logique de toute façon.

Lorsque vous allouer quelque chose, vous voulez dire le système de combien d'octets à allouer (pour des raisons évidentes).

Toutefois, lorsque vous avez déjà attribué votre objet, la taille de la mémoire de la région, vous obtenez en retour est maintenant déterminé. Elle est implicite. C'est un bloc contigu de mémoire. Vous ne pouvez pas libérer une partie de celui-ci (oublions realloc(), ce n'est pas ce qu'il fait de toute façon), vous pouvez seulement de libérer la totalité de la chose. Vous ne pouvez pas désallouer X octets" -- vous qu'il soit libre le bloc de mémoire que vous avez obtenu d' malloc() ou vous n'avez pas.

Et maintenant, si vous voulez vous libérer, vous pouvez juste dire que le gestionnaire de mémoire système: "voici ce pointeur, free() le bloc qu'il désigne." - et le gestionnaire de mémoire permettra de savoir comment le faire, soit parce qu'il implicitement connaît la taille, ou parce qu'il peut même pas besoin de la taille.

Par exemple, la plupart des implémentations typiques de l' malloc() maintenir une liste de pointeurs vers libre et alloué des blocs de mémoire. Si vous passez un pointeur vers free(), il suffit de chercher le pointeur dans la "alloué", liste, onu-lien, le nœud correspondant et de le joindre à la "libre" de la liste. Il n'a même pas besoin de la dimension de la région. Il aura seulement besoin de cette information lorsque potentiellement les tentatives de ré-utiliser le bloc en question.

14voto

ninjalj Points 22026

En fait, dans l'ancien noyau Unix allocateur de mémoire, mfree() a eu un size argument. malloc() et mfree() gardé deux tableaux (un pour la base de la mémoire, un autre pour le swap), qui contenait des informations sur des adresses de blocs et de tailles.

Il n'y avait pas l'espace utilisateur de l'allocateur jusqu'à Unix V6 (programmes voudrais juste utiliser sbrk()). Dans Unix V6, iolib inclus un allocateur avec alloc(size) et free() d'appel qui n'a pas à prendre une taille au-argument. Chaque bloc de mémoire a été précédée par sa taille, et un pointeur vers le bloc suivant. Le pointeur n'a été utilisé que sur des blocs libres, lors de la marche de la liste libre, et a été réutilisé comme bloc de mémoire sur les blocs en cours d'utilisation.

Dans Unix 32V et Unix V7, cela a été remplacé par un nouveau malloc() et free() mise en œuvre, où l' free() de ne pas prendre un size argument. La mise en œuvre a été une circulaire de la liste, chaque morceau a été précédé par un mot qui contient un pointeur vers la partie suivante, et un "occupé" (attribué) bits. Donc, malloc()/free() n'ont même pas garder une trace explicite de la taille.

13voto

C ne peut pas être comme "abstrait" comme le C++, mais il est toujours destiné à être une abstraction au-dessus de l'assemblée. À cette fin, le niveau le plus bas les détails sont pris en dehors de l'équation. Cela vous évite d'avoir à furtle sur l'alignement et le rembourrage, pour la plupart, ce qui ferait de tous vos programmes en C non portable.

En bref, c'est l'ensemble de point de l'écriture, une abstraction.

10voto

utnapistim Points 12060

Pourquoi est - free dans C de ne pas prendre le nombre d'octets à être libéré?

Parce qu'il n'a pas besoin d'. L'information est déjà disponible dans la gestion interne effectuée par malloc/free.

Voici deux considérations (qui peut ou peut ne pas avoir contribué à cette décision):

  • Pourquoi vous attendez-vous à une fonction de recevoir un paramètre qu'il n'en a pas besoin?

    (ce serait compliquer pratiquement tous les codes du client, en s'appuyant sur la dynamique de la mémoire, et complètement inutile d'ajouter de la redondance à votre demande). Garder la trace de pointeur de l'allocation est déjà un possible problème. Garder la trace des allocations de mémoire ainsi que les tailles augmente la complexité du code de client inutilement.

  • Ce serait la modification de l' free fonction de faire, dans ces cas?

    void * p = malloc(20);
    free(p, 25); // (1) wrong size provided by client code
    free(NULL, 10); // (2) generic argument mismatch
    

    Ne serait-il pas libre (provoquer une fuite de mémoire?)? Ignorer le second paramètre? Arrêter l'application en appelant à la sortie? La mise en œuvre de ce serait ajouter des points de rupture dans votre application, pour une fonctionnalité que vous n'avez probablement pas besoin (et si vous en avez besoin, voir mon dernier point, ci - dessous - "la mise en œuvre de la solution au niveau de l'application").

Plutôt, je veux savoir pourquoi free a été fait de cette façon en premier lieu.

Parce que c'est la "bonne" façon de le faire. Une API devrait exiger que les arguments nécessaires pour s'acquitter de son fonctionnement, et pas plus que cela.

Il se produit également pour moi que donnant explicitement le nombre d'octets de libre pourrait permettre une optimisation des performances, par exemple, un allocateur qui a des piscines séparées pour les différents répartition des tailles serait en mesure de déterminer la piscine gratuit de juste en regardant les arguments d'entrée, et il y aurait moins d'espace supplémentaire dans l'ensemble.

Les moyens appropriés à mettre en œuvre, sont les suivants:

  • (au niveau du système) dans la mise en œuvre de malloc - il n'y a rien d'arrêter la bibliothèque du maître d'œuvre de l'écriture malloc à utiliser diverses stratégies en interne sur la base reçus de la taille.

  • (au niveau de la demande) par emballage de malloc et free au sein de votre propre Api, et utilise à la place (partout dans votre demande que vous pourriez avoir besoin).

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