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_t
s 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_t
s 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.